Getting Started - ni3po42/traction.mvc GitHub Wiki

  1. Referencing
  2. Hello World
  3. Static Fragments v. Dynamic Fragments
  4. Observable Objects
  5. Observable Commands
  6. Observable Lists
  7. Observable Cursors
  8. Working in the UI
  9. Reserved Properties
  10. Advanced

This is the first of a series of posts that will describe how to use Traction MVC in your project. You can always refer to the Demo application included for specific examples. These posts are here to explain how to use different features and also help explain other poorly documented features. It will start will simple examples and move onto more advanced situations.

You can build Traction MVC from source or include the AAR file included in the latest release when referencing it in your application (the project will be available via maven in the future).

Top

Time for a hello world application! (This is not in the demo, it's probably the most watered down example I can make)

Create a new android application in Android Studio and reference Traction MVC as described above.

Now, create an interface that will represent your model (sometimes I will use the term 'scope' when referring to the model). You will use POJO style methods for denoting properties for your model like so:

package com.your.application.namespace.here;

public interface MyModel
{
	String getMyString();
	void setMyString(String str);
}

Create a new LinearLayout in the layout folder called main.xml like so:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"    
    android:tag="{@IsRoot:true, @model:'com.your.application.namespace.here.MyModel'}" >

    <TextView        
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
		android:tag="{Text:MyString}" />

</LinearLayout>

Bindings are defined in the 'tag' attribute of the layouts. The format used is JSON (internally it's using JSON.org implementation that is very lenient on syntax. Formally, we should use something like "{'@IsRoot':true}" and "{'Text':'MyString'} but the JSONObject will parse the keys and values as strings so long as there aren't any crazy characters like ':', ',', '{', '}', ....).
There are several reserved properties Traction will keep an eye out for. These will always start with '@' to distinguish them from normal view level properties. Concerning the above example:
'@IsRoot' is a reserved key and tells Traction where to begin binding the model at (there are other places '@IsRoot' will be used, but for now, just know you need to add it to the root of your layouts).
'@model' is a reserved key that tells traction what interface to inflate and expose as its scope. Traction will handle creating an instance and wiring it up to interact with the views.
'Text' on the TextView is a custom property that Traction knows about. It knows when a TextView is being processed, it checks if a 'Text' property is bounded. It will remember the path to watch for changes, too.
Now, create a new activity that inherits from 'traction.mvc.controllers.ActivityController': (add the activity into your manifest file like you normally would)

package com.your.application.namespace.here

import traction.mvc.controllers.ActivityController;

public class MainActivity extends ActivityController
{
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {	
            super.onCreate(savedInstanceState);
			//set the view the controller will interact with
            View.setContentView(R.layout.main);
		}
		@Override
		protected void onStart()
		{
			super.onStart();
			MyModel model = View.getScope();
			
			model.setMyString("Hello World!");            
        }
}

Important things to note here:

  • We use a mysterious 'View' object. That is defined in the ActivityController. You can take a look at how this class was implemented if you wish to integrate Traction with an existing base class.
  • In onCreate, we defined which view we want to use, and only passed the integer value for the main layout; we did not inflate or create a view ourselves here. Traction requires that views are not created outside the inflater unless you are building a custom view, which will be discussed in another post.

When this is run, you should expect a single line of text in the screen that says 'Hello World!'.

This is a very simple example using an activity as a controller. But we'll see fragments can work the same way.

Top

In the above example, I used the activity as the controller and interacted with the scope from there. Fragments can be used just as well.
To wire static fragments (these are fragments that have their classes defined in a layout file), implement it just like you normally would, except:
  <LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:tag="{@IsRoot:true}"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
&lt;fragment 
    android:name="your.package.qualified.name.here"
    android:layout_weight="1"
    android:layout_width="0dp"
android:layout_height="match_parent" 
android:tag="StaticViewModel" /&gt;

</LinearLayout>

you must include the path to a property in the 'tag' attribute that your code can access. Like in the example above, if this layout is used in an activity, the UI is expecting that activity to have a property called 'StaticViewModel' with a type that inherits from amvvm.viewmodels.ViewModel . This allows you to implement this:

public class MainActivity extends ActivityViewModel
{

	public ViewModel getStaticViewModel()
	{
		return getViewModel("StaticViewModel");
	}
    ...
}

Where 'getViewModel' is a method defined in ActivityViewModel that handles looking up static fragments.

I didn't want to hide too much away from the developer, so using dynamic fragments in amvvm work almost the same, except for a few conveniences. * You can use either FrameLayout or fragmentstub elements in your layout to 'stub' a location for a fragment. Fragmentstub is only a placeholder, and amvvm treats it just like a framelayout element; this was added to make it easier to understand what's going on in the layout. An id is required. Like below:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
  	android:tag="{@IsRoot:true}" >
&lt;fragmentstub
    android:id="@+id/main.view.id"
     android:layout_width="match_parent"
    android:layout_height="match_parent"
     /&gt;

</FrameLayout>

* A handy method was added to lookup and keep track of your fragments, whether they are currently attached or not:
public class MainActivity extends ActivityViewModel
{
        ...
public ViewModel getMainViewModel()
{
	return getViewModel("MainViewModel");
}

public void setMainViewModel(ViewModel mainViewModel)
{
	setViewModel("MainViewModel", mainViewModel);
}
    ...

}

You can 'get' and 'set' fragments (view-models, ofcourse). This action does not attach a fragment to the UI though, you will still need to do that, but it's easy:
public class MainActivity extends ActivityViewModel
{
        private boolean multiViewModelSupport = false;

        public ViewModel getMainViewModel()
        {
                return getViewModel("MainViewModel");
        }

        public void setMainViewModel(ViewModel mainViewModel)
        {
                setViewModel("MainViewModel", mainViewModel);
        }

        ...

        @Override
        protected void onCreate(Bundle savedInstanceState)
        {	
                super.onCreate(savedInstanceState);

                //the order below does matter! no static fragments can be load in the layout was not
                //set yet
                setContentView(R.layout.main);

               if (savedInstanceState != null)
                        return;
                
                boolean noMultiViewModelSupport =  (getMainViewModel() == null);
                        
                if (noMultiViewModelSupport)
                {
					MainViewModel mvm = new MainViewModel();
					...
					
					setMainViewModel(mvm);

                        getFragmentManager()
                                .beginTransaction()
                                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                                .replace(R.id.main_view_id, getMainViewModel())
                                .commit();
                }
        }
        ...
}

Above, we load in a layout (Note: unlike a previous example, the order you set the layout and try to get a fragment DOES MATTER), determine if our view-model is dynamic or static (a dynamic fragment will return null when 'getMainViewModel' is called; a static fragment will in fact have an instance now). In the code, if it's suppose to be a dynamic fragment, it calls the Fragment Manager and calls a replace with the id of the fragmentstub; this will attach the fragment to the UI. It also allows you to define the transitions and manage the back stack.

Top

Most of the objects used in AMVVM utilize some form of observer/listener patterns. Models (if you so chose to) and view-models implement an IObservableObject interface (actually, most will implement an IProxyObservableObject, but for the purpose of this current topic, we'll only be discussing IObservableObjects or their concrete counter parts, ObservableObjects). For one-way binding of data to the UI, you can use a POJO, as long as the properties are exposed as getters or as a public field. You can use any type object to also receive an update from the UI. What it cannot do is dynamic binding; that is if you use a POJO and at some point after the initial binding you update a property, the UI will not be aware of any changes. If you require the UI to react and update when a model updates a property, you will be required to implement IObservableObject (or implement IProxyObservableObject or inherit from ObservableObject).

Consider the following view-model and model:

public class SimpleFormViewModel extends ViewModel
{
	...

	public SimpleFormViewModel()
	{
		
	}

	public final UserInfo CurrentUserInfo = new UserInfo();
    ...

	@Override
	public void onCreate(Bundle savedInstanceState)
	{	
		super.onCreate(savedInstanceState);

		//the order of the following calls will not matter since UserInfo is an observable
		...
		//update userInfo properties here
		...
		
		setContentView(R.layout.userinfo);
	}
}
...

public class UserInfo extends ObservableObject
{
	...
	
	private String firstName;
	private String lastName;
	private Time dateOfBirth;
	
	...

	public UserInfo()
	{

	}
	
    ...

	public String getFirstName()
	{
		return firstName;
	}

	public void setFirstName(String firstName)
	{
		this.firstName = firstName;
		notifyListener("FirstName");
	}

	public String getLastName()
	{
		return lastName;
	}

	public void setLastName(String lastName)
	{
		this.lastName = lastName;
		notifyListener("LastName");
	}

	public Time getDateOfBirth()
	{
		return dateOfBirth;
	}

	public void setDateOfBirth(Time dateOfBirth)
	{
		this.dateOfBirth = dateOfBirth;
		notifyListener("DateOfBirth");
	}
}

A few things to note here:

  • The view model as a static final field 'CurrentUserInfo'. This is OK to use if the instance of the model will live for the life time of the view-model. amvvm will take care of setting up listeners between the model and view-model for you in this case. Using a getter/setter for this model would work equally as well, and should be used if you can expect the instance of UserInfo to change throughout the lifetime of the view-model (lets say there were different users to look up, and that lookup resulted in the creation of a new UserInfo object). It would look kinda like this:
    ...
        private UserInfo currentUserInfo;
        public UserInfo getCurrentUserInfo()
    	{
    		return  currentUserInfo;
    	}
    
    public void setCurrentUserInfo(UserInfo currentUserInfo)
    {
    	notifyListener("CurrentUserInfo",this.currentUserInfo,this.currentUserInfo = currentUserInfo);
    }
    

    ...

    Even if a UI element is pointing to a particular property on the 'CurrentUserInfo' object, changing the 'CurrentUserInfo' object will info the UI of a change and automatically request updated values of it's properties.
  • 'UserInfo' is an 'ObservableObject' and exposes several properties. If an update to the property occurs, it will call the 'notifyListener' method of the 'ObservableObject' to notify anything listening (the UI for instance) that data is being updated.

Now, if the UserInfo class did not inherit from ObservableObject, the current implementation would not work; updating any field on the instance of CurrentUserInfo after it is bound to the UI will not inform the UI of any changes. (Now, there is a way to get this to 'work' without it being an ObservableObject, and that is by implenting the getter/setter for the CurrentUserInfo propety like above. Updating the whold UserInfo object will force the UI to update all properties it's looking for; but again, independent updates to properties of UserInfo will go unnoticed).

You should balance the need to use an ObservableObject to a non-observable objects when designing your application. Some cases you don't need an ObservableObject (like for spinners/drop downs; this is one case you should NOT used observables because that data is expected to be static).

Making an object 'observable'

What do you do if you have an existing object that 'needs' to be observable, but it already inherits from another base class?
This was anticipated early on. Amvvm has a built in design to allow your objects to be easily 'wrapped' and become observable. In fact, that's exactly how fragments and activities within amvvm are observable. Here's how:
(I don't know the name of this pattern, and couldn't easily find one, so I apologize for not giving credit to the individual that might have first thought of this, but I independently implemented the design and it seems to work quite well)
public class MyCustomBaseClass
 extends SomeOtherBaseClassThatIsNotObservable
 implements IProxyObservableObject
 {
   private final PropertyStore store = new PropetyStore();
   private final ObservableObject internalObj = new ObservableObject()
   {
     @Override
	 public IPropertyStore getPropertyStore()
	 {
	   return store;
	 }
	 
	 @Override
	 public Object getSource()
	 {
	   return MyCustomClass.this;
	 }	 
   };
 
   @Override
   public IObservableObject getProxyObservableObject()
   {
     return internalObj;
   }   
 }

So, first you will see something called 'PropetyStore'; for now, just know that it's a cache that stores information about the properties availible and gives a uniform way of accessing them. (Sometimes they can be static, but for this example, it isn't)
The class implements the amvvm.interfaces.IProxyObservableObject interace. The 'getProxyObservableObject' should return your proxy object that will stand in for your ObservableObject. 'getSource' lets the framework know that object to use to get/set properties for. The proxy observable object that is created, 'intenalObj', requires a PropertyStore to be defined and also needs a source (this case, we'll just use whatever the outer class wants to use, which in this case is 'this').
Now, any class inheriting from this class can do this:

//assumes that 'SomeOtherBaseClassThatIsNotObservable' has a get/set property for 'Name' that is not final
public class Myclass
 extends MyCustomBaseClass
 {
	@Override
   public void setName(String name)
   {
     super.setName(name);
	 getProxyObservableObject().notifyListener("Name");
   } 
 }

MyClass is now observable, and can notify other objects when 'Name' updates. If you don't like calling 'getProxyObservableObject()' every time you need to access the observable object calls, your base class can implement IObservableObject completely, but this example just shows a quick way of accomplishing the same task. To see full implememtated ObservableObjects, check out ViewModelHelper and ViewModel.

Top

There are different ways the UI can interact back with the view-model, and one of those ways is to define a Command. A command is a hook the UI can attach to and call upon (depending on the functionality of the UI element).
public class ExampleViewModel extends ViewModel
{
	public final SimpleCommand MyCommand = new SimpleCommand()
	{
        @Override
        protected void onExecuted(CommandArgument commandArgument)
        {
            //perform some type of action...
        }
    };

	@Override
	public void onCreate(Bundle savedInstanceState) 
	{
		super.onCreate(savedInstanceState);
		//set the layout with button here
		setContentView(...);
	};
}

Above is a simple view-model that defines a custom command. Suppose it associates to a view like this one below:

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"

    android:tag="{@IsRoot:true}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"     
    android:orientation="vertical" >
            
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Do Someting"
        android:tag="{OnClick:MyCommand}" />

</LinearLayout>

Then when the button is clicked, the command in the view-model will fire (depending on the element, it may or may not have an argument to pass over).
A 'SimpleCommand' is a convenience class based on the 'Command' class that is derived from 'ObservableObject'. Aside from responding to events, it also exposes a 'CanExecute' property that the UI (or anything) can listen to, or simply control the visibility of elements (like buttons) when the command is valid to be executed.
In situations where you might have lots of commands and it might be easier to group the actions of these commands together, instead of overriding the 'onExecuted' method, you can set an IOnExecutedListener object (located in amvvm.implementations.observables.Command.IOnExecutedListener) to the command instead:

public class Example_2_ViewModel 
	extends ViewModel
	implements IOnExecuteListener
{
	public final SimpleCommand MyCommand1 = new SimpleCommand();
	public final SimpleCommand MyCommand2 = new SimpleCommand();
	public final SimpleCommand MyCommand3 = new SimpleCommand();

	public Example_2_ViewModel()
	{
	  MyCommand1.setOnExecuteListener(this);
	  MyCommand2.setOnExecuteListener(this);
	  MyCommand3.setOnExecuteListener(this);
	}
	
	public void onExecuted(ICommand.CommandArgument arg)
	{
		String commandName = arg.getCommandName();
		//commandName could be MyCommand1, MyCommand2 or MyCommand3
		...
	}
	...
}

It's also possible to have more than one element pointing to a single command. If you need to differentiate them or simply pass a value along, you can add a custom property in the view's JSON tag and reference that value from the CommandArgument parameter. The calculator example in the Demo is a good example of this at work: most input point back to the same command, but pass a different custom value in a 'CommandValue' property so the view model can handle the commands differently.

Top

We've seen how to bind to objects and properties, but what about collections of data? The usual Android way of handling collections is to create an adapter and connect the data into the adapter; the adapter is then set to a view that handles adapters (like ListView or Spinner). Adapters don't quite fit into the MVVM spirit; They bind data, however they require you do define view infomation within the adapter. So the solution was to 'hide' the adapter within another object, protecting the developer from accessing the view related stuff in the adapter, but take advantage of the work the adatper can accomplish: so the ObservableList was born. The goal of this document is not to explain the inner workings of the framework, but to demonstrate the uses of it, so I will gloss over how the ObservableList binds to an adapter view and get on with it.
Examine the code below:
public class ListViewModel extends ViewModel
{
public static class DemoModel
{
	private String text;
	private int id;
	public String getText(){return text;}
	public int getId(){return id;}
	public DemoModel(String text, int id){this.text=text;this.id=id;}
}

public final ObservableList&lt;DemoModel&gt; MyList = new ObservableList&lt;DemoModel&gt;(new ArrayList&lt;DemoModel&gt;());

@Override
public void onCreate(Bundle savedInstanceState)
{	
	super.onCreate(savedInstanceState);

	if (savedInstanceState != null)
		return;

	MyList.add(new DemoModel("TEST", 1));
	MyList.add(new DemoModel("FOO", 2));
	MyList.add(new DemoModel("BAR", 3));

	//set view. Order not important here, you can populate the list before or after you set the layout.
	setContentView(...);
}

}

First, you can see we defined an ObservableList property. It too is registered automatically if it is a final field that is assignable from IProxyObservableObject. However, the constructor requires an object that implements List: it uses this list internally to store and access the list (this allows you to define the type of list you may need in your application). ObservableList implements List too, so you can use it just like a normal list. The ObserableList exposes a property called 'Count'. 'Count' is just a wrapper for the 'size()' method call, but 'Count' fits the semantics to allow the UI or other objects to listen for updates.
Also note that 'DemoModel' is not an ObservableObject, it's a regular object. This is ok as long as you are not expecting individual updates to the properties in DemoModel. This will be further explained when AdapterViews are covered more closely in a later section.
Now, lets look at the layout

<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:choiceMode="singleChoice"
    
	android:tag="{@IsRoot:true, Items:MyList, ItemTemplate:'@layout/mylistitem'}" >
	
</ListView>

This time we have a ListView shown with 2 new properties: Items and ItemTemplate. 'Items' is the path to the ObservableList (or more correctly to an object that inherits from ProxyAdapter, which will come up in ObservableCursors in the next section) that you wish to bind. But you also must define the layout for each item in the list, that is what 'ItemTemplate' does. It will point to a layout resource of your design/choosing, like below for instance:

	<-- mylistitem.xml -->
	<GridLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"    
    android:tag="{@IsRoot:true}"
    
   	android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:useDefaultMargins="true"
    android:alignmentMode="alignBounds"
    android:columnOrderPreserved="false"
	android:rowCount="2"
    android:columnCount="1">
    
    <TextView        
        android:layout_row="0" android:layout_column="0" 
	  	android:layout_gravity="left"  
		android:layout_height="25dip"
		android:textStyle="bold"
		android:tag="{Text:Id}" />
    
     <TextView 
        android:layout_row="1" android:layout_column="0"
       	android:layout_gravity="left"
  		android:layout_height="wrap_content"
  		android:tag="{Text:Text}"  />
</GridLayout>

Things to note:
This item template has an "@IsRoot' property set to true. This is a requirement for layouts to be templates for AdapterViews.
The path for the TextView's text is not relative to the view-model, but now relative to the DemoModel itself. You can access properties up the hieracrchy, which will be covered in a later section of this document. (At this time, handling different templates based on the type or data of an object in the list (viewtype) is not supported, but will be in a future update. For now, the workaround is to create a custom view as your template and have it dynamically update the template structure as you need. Custom views will be explained in a later section).

Top

Observable Lists are fine for small static data and well defined models, but what about the other cases? What if you need to query a large amount of data to display in a ListView? If you retrieved a cursor, you could load it into an adapter and drop it in the ListView; the list view is smart enough to sort it's way though that cursor. If you try this with an ObservableList, you'd likely find yourself with serious performance issues. Also, you'd have to build a model to represent the different resulting data set for each query you do. There is a better way: Observable Cursors!

An Observable Cursor is actually an ObservableObject that can load a cursor internally.

public class CursorListViewModel
    extends ViewModel
	implements IObservableCursor.ICursorLoader
{
    public final ObservableCursor MyCursor = new ObservableCursor();

    public CursorListViewModel()
    {
        MyCursor.setCursorLoader(this);
    }

	@Override
	public Loader<Cursor> onCreateLoader(Bundle arg)
	{
		return new CursorLoader(getActivity(), MyProvider.CONTENT_URI, null, null, null, null);
	}
	
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(...);
        getLoaderManager().initLoader(0, null, MyCursor);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig)
    {
        super.onConfigurationChanged(newConfig);
        setContentView(...);
        getLoaderManager().restartLoader(0,null, MyCursor);
    }
}

There is a little more stuff to worry about when handling cursors, but I tried to keep it simple. You can create a final static field like you normally do; however, you also need to set an ICursorLoader object for your ObservableCursor. This allows you to define a handle to acquire a Loader<Cursor> object that you'll need to query/retrieve data. You get the LoaderManager from the view-model and initialize your loader (ObservableCursor implements LoaderCallbacks, so you can pass it as your callbacks parameter on initLoader/restartLoader) in the 'onCreate' method. The reason for the onConfiguratinoChanged is for handling configuration changes; Android is now handling your cursor, and it'll need to know to reload it if, for example, you rotate your screen or it gets resized.
The layout works the same: Items and ItemTemplate attributes need to be filled. The ItemTemplate can reference the properties by their column names.

ObservableCursors only support read-only binding. In a future update amvvm will have the option to track changes in ContentValues.

Experimental ObservableCursors may work without a AdaperView to populate it; for example, they are built to bind like ObservableObjects, but have never been tested enough to confirm if any adjustments are needed to gain this functionality.

Top

Activity and fragment code becomes real messy with when a lot of UI code is needed. Refactoring all that into views does help, but still involves some code to handoff data between the view and the activity/fragment. The examples so far showed how to remove the UI references from your activities and fragments. This segment shows how to create custom UI that works with amvvm.
There are four different ways to hook a widget into the amvvm framework:
  • Implicit Binding
  • Implicit Extension
  • Explicit Binding
  • Explicit Extension
(Don't worry, no quiz on this; just shows you have different choices)

Implicit Binding

This describes the type of binding you've seen so far in this document. A vanilla android widget is added to a layout and the framework handles the different types of properties it can bind with out of the box.

Implicit Extension

You extend a widget/view from the android framework and add that custom view to a layout. If the framework supports the base (or any ancestor) of your class, it will implicitly support the same properties: no extra work required.

Explicit Binding

Suppose amvvm has a default binding for a widget/view that is causing problems with your design. If you don't want to disable it, you can explicitly override it with the '@BindingType' property. '@BindingType' is available for all widgets that inherit from 'View'. Simply create a class that implements IViewBinding (or IProxyViewBinding) and use that class name as the value of '@BindingType'. This will allow you to change how that view is bound against a model and optionally include more properties to bind against. If you don't want to re-invent the wheel, you can optionally extend from the 'GenericViewBinding'; this will by default give you the 'IsVisible' property, or implement 'IProxyViewBinding' and use the 'ViewBindingHelper' as your proxy.
Here is an example of Explicit Binding from the framework:
public class TimePickerBinding 
extends GenericViewBinding<TimePicker>
{
    public final UIProperty<Time> SelectedTime = new UIProperty<Time>(this, "SelectedTime");
private static final OnTimeChangedListener timeChangedListener = new OnTimeChangedListener()
{
    @Override
    public void onTimeChanged(TimePicker picker, int hourOfDay, int minutes)
    {
        TimePickerBinding tpb = (TimePickerBinding)ViewFactory.getViewBinding(picker);
        if (tpb == null)
            return;

        Time t = new Time(tpb.SelectedTime.dereferenceValue());
        t.set(0, minutes, hourOfDay, t.monthDay, t.month, t.year);
        tpb.SelectedTime.sendUpdate(t);
    }
};

public TimePickerBinding()
{
		SelectedTime.setUIUpdateListener(new IUIElement.IUIUpdateListener&lt;Time&gt;()
		{
				@Override
				public void onUpdate(Time value)
				{
						if (getWidget() == null || value == null)
								return;                                                                
						getWidget().setCurrentHour(value.hour);
						getWidget().setCurrentMinute(value.minute);                                
				}
		});
		
}

@Override
protected void initialise()
{
    super.initialise();
    getWidget().setOnTimeChangedListener(timeChangedListener);     
 }

@Override
public void detachBindings()
{
		getWidget().setOnTimeChangedListener(null);
		super.detachBindings();
}

}

Here we create a ViewBinding that can wire a 'Time' property to a 'TimePicker' widget.

Explicit Extension

Need to build an awesome widget that does a type of binding that no one has ever thought of? If you are having to do both, then you can do them in the same object. Explicit Extension allows you do define a custom widget/view and have it implement IProxyViewBinding. When used, you will not need to use '@BindingType' to define your view's binding, but it will be defined in the view/widget itself (it's implicit binding, but you customized how it binds).
Here's an example from the Demo application:
public class SwipeEntryView
    extends LinearLayout
    implements IProxyViewBinding, SwipableListView.ISwipable, Animator.AnimatorListener
{
private final ViewBindingHelper helper = new ViewBindingHelper&lt;SwipeEntryView&gt;()
{
    @Override
    public SwipeEntryView getWidget()
    {
        return SwipeEntryView.this;
    }
};

public UIProperty&lt;Boolean&gt; Active = new UIProperty&lt;Boolean&gt;(this, "Active");

...

public SwipeEntryView(Context context, AttributeSet attrs)
{
    super(context, attrs);
    init(context);
}

...

private void init(Context context)
{
    Active.setUIUpdateListener(new IUIElement.IUIUpdateListener&lt;Boolean&gt;() {
        @Override
        public void onUpdate(Boolean value) {
            if (value == null)
                return;
            setBackgroundColor(value ? 0x00000000 : 0xFFCCCCCC);
        }
    });
}

@Override
public void onLeftSwipe()
{
    ObjectAnimator outAnimator = ObjectAnimator
            .ofFloat(this, "x", -getWidth())
            .setDuration(250);
    outAnimator.addListener(this);
    outAnimator.start();
}

...

@Override
public IViewBinding getProxyViewBinding() {
    return helper;
}

@Override
public void onAnimationEnd(Animator animator) {
    ObjectAnimator inAnimator = ObjectAnimator
            .ofFloat(this, "x", this.getWidth(), 0)
            .setDuration(250);

    Boolean value = Active.dereferenceValue();
    if (value != null)
    {
        Active.sendUpdate(!value);
        setBackgroundColor( !value ? 0x00000000 : 0xFFCCCCCC);
        inAnimator.start();
    }
}
...

}

(some boiler plate code was excluded, please refer to the Demo application for the full implementation. Also, it might have been better for this widget to implement Checkable instead and will probably be updated in future versions of the Demo application.)
This example lets you create a view that can slide left when swiped and toggles a boolean property of a view-model/model

Top

As noted earlier, there are a collection of reserved properties for the JSON tag data. Below is a list of reserved properties and their uses:
  • @IsRoot
    Boolean value; when set to true the view factory will treat the view as the root of the hierarchy
  • @IgnoreChildren
    Boolean value; when set to true the view factory will ignore all view bindings that are a child to that view. This will help optimize the parsing/binding for more complex views.
  • @BindingType
    String value; a fully qualified type that implements IViewBinding. The view factory will use this binding instead of the default.
  • @RelativeContext
    Object path; All bindings from this view and all it's children view will be relative to the object path set in this property.
  • @Properties
    JSON object type; For interacting with getter/setter methods on a view (or what will be referred to as 'generic properties'). Each key will be a getter/setter type property and the associated value will be an object path to bind to.
  • @Events
    JSON object type; For interacting with event listeners on a view (or what will be referred to as 'generic events'). Each key will be the name of a method that registers an event and the associated value will be an array of object paths to bind to.
  • @meta (future)
    JSON object type; For storing general data that can be accessed from the view-models.
  • @tag (future)
    String; to still allow other code to access the 'tag' value without AMVVM getting in the way, you may set the @tag property to the intended value and the view factory will reset the 'tag' value once it has retrieved the JSON tag data
  • @model (future)
Top Soon...

Performance considerations

Relative Context

Binding 'this'

Top

⚠️ **GitHub.com Fallback** ⚠️