AndroidXml

gem install android-xml

Quick start

generate_xml.rb
require 'android-xml'

AndroidXml.file('res/values/strings.xml') do
  resource do
    string(name: 'app_name') { 'AppAppApp' }
  end
end

AndroidXml.run
> ruby generate_xml.rb
✓ res/values/strings.xml

> cat res/values/strings.xml
<!-- Do not edit this file. It was generated by AndroidXml. -->
<resources>
    <string name="app_name">AppAppApp</string>
</resources>

About

With AndroidXml you can generate an XML file with much less fuss. It will take care of prefixing attributes with android: when required, it includes the <?xml ?> tag, includes the xmlns: attribute on your root node (but only when required)... all that good stuff!

You still need to understand the format that the various Android XML files expect, but hopefully you get a good productivity boost.

More examples

# The dreaded AndroidManifest file!
AndroidXml.file('AndroidManifest.xml') do
  manifest package: 'com.your_name_here.AppAppApp', versionCode: 1, versionName: 1.0 do
    uses_sdk minSdkVersion: 11

    application label: '@string/app_name', icon: '@drawable/ic_launcher' do
      activity name: 'MainActivity', label: '@string/app_name' do
        intent_filter do
          # these helpers are defined in `android-xml/defaults.rb`,
          # and you can easily add your own with an AndroidXml.setup block (see
          # below)
          main_action
          launcher_category
        end
      end

      activity name: 'DisplayMessageActivity',
        label: '@string/app_name',
        parentActivityName: 'com.your_name_here.AppAppApp.MainActivity'
    end
  end
end

# You can generate multiple files from one .rb file
AndroidXml.file('res/values-jp/strings.xml') do
  resource do
    string(name: 'app_name') { 'アッピアッピアッピ' }
  end
end

# If you want, you can replace `file` with the root node name
AndroidXml.resource('res/values/strings.xml') do
  string(name: 'app_name') { 'AppAppApp' }
end

# `clean_up` finds files that were generated by AndroidXml and removes them.
# Files created by `write_all` are not removed.  Accepts a string or array of
# strings.
AndroidXml.write_all
AndroidXml.clean_up 'res/'
# Outputs any missing string tags (looks for @string/name attributes and
# <string name="name"> tags)
AndroidXml.missing_strings?

# Calling AndroidXml.run will call write_all, clean_up, and missing_strings?

Setup / Helpers

Here's how we create the main_action helper, or if you need to specify that some attributes don't need the android: prefix.

AndroidXml.setup do
  # the hash syntax specifies the `shortcut => actual-tag-name`
  tag :main_action => 'action' do
    # then we can assign default attributes
    defaults name: 'android.intent.action.MAIN'
  end
  tag :style do
    # <style name="..."> is correct - NOT <style android:name="...">
    rename :name
  end

  # if you want to add your own shorthands, go nuts:
  tag :activity do
    rename :parent => :parentActivityName
  end

  # global changes
  all do
    rename :style  # always style="", never android:style=""
  end

  # adds the xmlns attribute to the root node, no matter WHAT the root node is
  root do
    defaults 'xmlns:android' => 'http://schemas.android.com/apk/res/android'
  end

  # disable the xmlns attribute on the resource node
  tag :resources do
    defaults 'xmlns:android' => nil
  end
end

Re-using tags

When building a layout with variations for landscape/portrait/size, you will often have big chunks that are reusable. Let's say you have a button that is in a FrameLayout, and centered at the bottom:

# create a <Button> tag
close_button = AndroidXml.Button(
  id: '@+id/close_button',
  layout_width: 'wrap_content',
  layout_height: 'wrap_content',
  layout_gravity: 'bottom|center',
  text: '@string/close_button'
  )

# and include it in a layout
AndroidXml.file('res/layout/some_activity.xml') do
  RelativeLayout(...) do
    # ...
    include close_button
  end
end

You can clone a tag and make changes to it:

AndroidXml.file('res/layout-land/some_activity.xml') do
  RelativeLayout(...) do
    # ...

    include close_button(padding: 8.dp)
  end
end

You can create a tag with sub nodes, too!

warning = AndroidXml.LinearLayout(
  layout_width: 'match_parent',
  layout_height: 'match_parent',
  orientation: 'horizontal'
  ) do
  Image(padding: 24.dp, src: '@drawable/warning_icon')
  TextView(layout_width: 'wrap_content',
    layout_height: 'wrap_content',
    text: '@string/warning_text')
end

AndroidXml.file('layout.xml') do
  LinearLayout layout_width: 'match_parent',
    layout_height: 'match_parent',
    gravity: 'center',
    orientation: 'vertical' do
      include warning

      # if you want to replace the contents:
      include warning.clone do
        Image(padding: 24.dp, src: '@drawable/error_icon')
        TextView(layout_width: 'wrap_content',
          layout_height: 'wrap_content',
          text: '@string/error_text')
      end
  end
end

Note: this could also be accomplished using AndroidXml.setup, but setup is meant for global conventions. Layout-specific things should be included with include, in my opinion. But, up to you:

AndroidXml.setup do
  tag :close_button => 'Button' do
    defaults id: '@+id/close_button',
      layout_width: 'wrap_content',
      layout_height: 'wrap_content',
      layout_gravity: 'bottom|center',
      text: '@string/close_button'
    contents do
    end
  end
end

Rakefile to build the XML files

Generates all files that are in the ./android-xml folder.

Rakefile
require 'rake'
require 'android-xml'
# require 'bundler'
# Bundler.require


task :default => :generate

desc 'Generate XML files'
task :generate do
  # the :in option can point to a folder of AndroidXml files (subdirectories
  # are included)
  AndroidXml.run(in: 'android-xml')
end

Errata

# If you want to check the output before committing to `write_all`
AndroidXml.output_all

# If you want to wipe out all the defaults and settings
AndroidXml.reset

# If you want the defaults back
AndroidXml.setup_defaults