VaporX Groupings - jQuery Style Selectors for Android

Another powerful feature of the Vapor Android framework is VaporX, the ability to control groups of related components simultaneously.

This idea is analogous to jQuery selectors that save you having to replicate code when you wish to perform the same action on multiple objects at once.

This is useful because you can group any arbitrary combination of related components, and can instantiate a VaporX type using any mixture of acceptable selector types.

The acceptable selector types varies depending on the flavor of VaporX you are using so check the applicable section for more details.

Flavors

As of the latest version of the Vapor Android framework, the following VaporX flavors exist:

  • VaporXView - used to control multiple related View components simultaneously
  • VaporXHook - used to control multiple Hooks simultaneously
  • VaporXFragment - used to control multiple Fragment components simultaneously

Restrictions

When using VaporX to group components that do not share the exact same type, you should use the most specific type that all components have in common.

For example, to group two Buttons and a TextView you would use $.VaporTextView(...), as VaporTextView is the most specific class that all items can be safely treated as polymorphically.

This of course then means the Vapor Android framework only lets you invoke methods defined in VaporTextView on the group, which is type safe.

Let's now look at a code example of this point. First, here is the compact and fluent Vapor framework code:

$.TextView(R.id.text,R.id.button1,R.id.button2)
.text("I just changed the text of all components!");

And here is the much longer, standard Android equivalent:

	TextView text = (TextView) findViewById(R.id.text);
	Button button = (Button) findViewById(R.id.button),
		   button2 = (Button) findViewById(R.id.button2);
	
	text.setText("I just changed the text of all components!");
	button.setText("I just changed the text of all components!");
	button2.setText("I just changed the text of all components!");

Even with this simple code one can see how the Vapor Android framework lets you type much less, without sacrificing on functionality.

VaporX Constructors

Like jQuery, each VaporX type constructor is variadic, and any item passed to it must be able to be safely treated as at least one of the acceptable selector types for a particular VaporX flavor.

Check out a specific VaporX flavor's section for the list of acceptable selector types for it.

Conveniently, the varargs list you supply to a VaporX constructor can be any mixture of the above acceptable types! For example, the following works:

$.Button(
    // an int id of a button
    R.id.button1, 
    // an existing 'T' instance, in this case Button
    someButton, 
    // an existing 'X' instance (underlying component type), here a VaporButton
    someVaporButton, 
    // an existing VaporX 'self' instance, in this case VaporXButton
    someVaporXButton 
);

Under-the-hood VaporX will take care of converting all of the selectors to the underlying component type, e.g. VaporButton. This is nifty because you can quickly group the related components you are working on, even if they are all in different forms like in the example above.

Return Types

'self' Methods

Methods that return self will be performed on all components in a VaporX group.

Currently the Vapor Android framework does not inherently protect against duplicates in a VaporX instance for efficiency reasons (given the O(n) overhead of such checks). Use .contains(X) to check for the presence of an item if required

Other Methods

If the method returns a result other than self, an aggregate genericized ArrayList of results will be returned.

In this genericized ArrayList the value at each index corresponds to the result from invoking the method on the component at that index in the underlying group.

VaporX $ Methods

If you didn't already know, the $ class is a facade central to the Vapor Android framework, and should be how you access the majority of the features in Vapor - just like in jQuery.

VaporX is no exception, and moreover, the $ methods will silently deduce whether you require a VaporX instance of a particular flavor, or just the singular type.

For example, you can use the same method regardless of whether you want to manage just a single component type, or VaporX group of Android components:

    // returns a singular VaporProgressBar
    $.ProgressBar(R.id.progress_bar); 

    // returns a VaporXProgressBar that groups instances identified by given selectors
    $.ProgressBar(R.id.progress_bar, someProgressBarInstance, R.id.another_progress_bar);

The Vapor Android framework silently infers whether you require a singular or VaporX instance based on how many selectors you supply to the relevant $ factory method.

This automatic inference has several useful advantages:

  • You can use the same overloaded $ factory method for both singular, and VaporX instantiation of a type
  • Unlike jQuery, the result type from a method invocation is as expected, and can be read without extra logic. ONLY VaporX methods return aggregate ArrayLists, singular Vapor components return singular values
  • All of the methods avaible on singular types are available on the VaporX companion type so you can interchangeably use both

Here is a code snippet of the above benefits:

	// returns a single result
    Integer result = $.NumberPicker(R.id.num_picker).val(); 
	
    // returns an aggregate parameterized ArrayList of results
	ArrayList<Integer> results = $.NumberPicker(R.id.num_picker, R.id.num_picker2).val();

We use the same $ factory method for both singular and VaporX types. Further, the result from invoking the 'same' method on both is of the expected type in each case.

If you supply just one selector a singular Vapor type is returned, for example VaporNumberPicker, whereas using more than one selector will return the VaporX companion type, e.g. VaporXNumberPicker

Custom Methods

VaporX also lets you apply your own custom methods to all members of the underlying group. There are two ways in which you can do this, depending on your needs:

each($each<X>)

The .each($each<X>) method is the simpler of the two options, in that the method you apply to members of the underlying set returns void. Users of jQuery will no doubt recognise this.

The .each($each<X>) method requires you pass it an instance of $each<X>, which can be compactly done using anonymous inline instances.

Inside the inner .each(X) method you put the code that you want applied to the constituent members of the VaporX instance.

This is all shown in the following example where we have a form made up of TextViews (name, address, phone and email):

	$.TextView(R.id.name,R.id.address,R.id.phone,R.id.email)

	    .each(new $$each<TextView>(){

            public void each(VaporTextView<? extends TextView,?> x){

			    CharSequence value = x.text();

			    // Convert text value to upper case
			    x.text(value.toUpperCase());
		    }
	    });

Here we use the inner .each(X) method to convert all values entered in to our form to uppercase.

We don't require a return value from each invocation so a void method suits us perfectly.

In many cases however, you will want to obtain a return value from your custom method. For this you can use .map($map<X, M>) instead, as explained below.

map($map<X, M>)

The .map($map<X, M>) method in the Vapor Android framework has a similar role to its use in functional programming, in that it aggregates the return values from applying the given method to all members of the underlying set.

The .map($map<X, M>) method takes a $map<X, M> instance as a parameter, in which you implement the inner .map(X) method with the code to apply.

$map<X, M> is parameterized by the component type of the underlying VaporX set (X), and the result type returned when executed (M).

Like with .each($each<X>) above, a neat way to pass this is as an inline anonymous instance. This is seen in the following example where we once again have a form of TextViews, and wish to validate and collect all the form data that the user has entered:

	ArrayList<String> formValidation = 

        $.TextView(R.id.name,R.id.address,R.id.phone,R.id.email)
            .map(new $$map<TextView, String>(){

                public String map(TextView x){

                    String value = $.TextView(x).text();

                    // if validation fails, show error to user
                    if(!valid(value)){

	                    $.TextView(x).color(Color.RED);

	                    return "Value entered is not valid";
                    }

                    return ""; // No validation error to report
                }
            });

In the above example we use the $.TextView(...) factory method to get a VaporXTextView containing our form fields.

On this VaporXTextView we call .map($map<X, M>) and pass an anonymous $map<X, M> instance parameterized by TextView and String, as TextView is the underlying component type and String is the return type we want to obtain from invoking the code on each member.

The result is an ArrayList<String> which contains the aggregated return values, indexed in the same order as the underlying set:

  • [0 → name]
  • [1 → address]
  • [2 → phone]
  • [3 → email]
Though the examples above only focus on View components, you can use .each($each<X>) and .map($map<X, M>) on any VaporX flavor you like!

Even simple examples such as these serve to demonstrate how powerful and easy writing your own methods for VaporX is.

This is another convenient feature where you save time and effort by developing your Android apps with the Vapor framework.