-
Notifications
You must be signed in to change notification settings - Fork 1
Insight: How to add a new Page properly
A Page is what the user sees and contains the views (buttons, text, layouts, etc) that the user can interact with. In short, it makes a large part of the view. But in order for a Page to work properly, some more work has to be done. I will talk about all these details here.
1.Page:
As I have mentioned in the technical overview, a Page (a ContentPage in particular) is always accompanied by at least one BackgroundTask. Here I will describe in detail how to properly set up and add a Page into MollyAndroid. First, there are a few things to talk about the structure of a Page itself. The methods of Page of interest for this particular document are:
public void onCreate (Bundle savedInstanceState) { ... }
public void onResume(){ ... }
public abstract String getQuery() throws UnsupportedEncodingException;
public abstract Page getInstance();
public String getAdditionalParams();
public String getName();
public abstract LinearLayout getContentLayout();
public abstract void setContentLayout(LinearLayout contentLayout);
The first 2 methods are closely related to the lifecycle of an Android application, and this is important: onCreate() is ALWAYS called before onResume when the Page is first created and it is called only once, and onResume() is called whenever the page is resumed. Manual refresh is needed from time to time, not mentioning the case the cached data is lost because it is claimed by the OS to transfer the memory to other hungry apps, so it is most sensible to put all the BackgroundTasks in onResume(). Some of the static elements in a Page can be put into onCreate(), but dynamically generated contents obviously goes into the BackgroundTasks.
The rest of the above mentioned methods are for setting up the parameters to be supplied to the Router for further queries and have to be present in any subclasses of Page. Here, you should have gotten yourself familiar with the way the domain of an app on Molly is structured. A good example is:
http://dev.m.ox.ac.uk/transport/rail/?board=departures
where:
-
“transport” is the Molly app
-
“rail” is the “slug”, i.e. the additional argument given to the reverse function on the server to get to the right part of the app
-
“?board=departures” is the query
To get the correct URL as above, Molly uses a “reverse” API: taking in the name of an app as known internally to the server and returns the actual URL, so in this case, getName() should return the name of the transport app on Molly, getAdditionalParams() should return the string: “&arg=” appended to whatever slug it needs, in this case, “rail” and the getQuery() method should return the query needed to send to the server - “?board=departures” asks for departures while “?board=arrivals” asks for arrivals. For more details on this API, you should refer to Molly itself. In order to make the code of subclasses cleaner, getName() actually returns a String name and getAdditionalArgs() returns the String additionalArgs. These Strings should be assigned in the onCreated() method.
A fully implemented page should extends a ContentPage and an unimplemented one should extends UnImplementedPage that simply loads a WebView of the page it is pointed to. For UnImplementedPage, there is no need to supply a second BackgroundTask like the others to update the views
The accompanying BackgroundTasks (called from the onResume() method) should make use of all the above methods to request the JSON data from the server via the Router, for example:
jsonContent = MyApplication.router.onRequestSent(page.getName(), page.getAdditionalParams(), Router.OutputFormat.JSON, page.getQuery());
And the rest of the hard work is now done by the Router.
2.BackgroundTasks:
A subclass of BackgroundTask should give it 3 inferred generic types, for example:
protected class PlacesTask extends BackgroundTask<JSONObject, Void, JSONObject>
these types can vary, but they stay <JSONObject, Void, JSONObject> mosackt of the time in this application. In fact, in Molly Android, there is a task class called JSONProcessingTask that extends BackgroundTask<JSONObject, Void, JSONObject>. For more details, you should refer to Android AsyncTask.
Now a BackgroundTask has 2 main basic methods:
protected JSONObject doInBackground(JSONObject... params) { ... }
public void updateView(JSONObject breadcrumbs) { ... }
The above call for querying the JSON data should only be done in the doinBackground() method of the BackgroundTask. This method allows a progress spinner to be present on the screen while it runs (a boolean in the task’s constructor will decide whether it appears or not.) However, this method MUST NOT violate any views at all – this can only be done from the UI thread (the thread that controls the Page’s lifecycle) and can, fortunately, be accessed from the updateView() method – so any necessary changes in the UI should be made in the updateView() method.
Notice that a BackgroundTask cannot be reused and has to be created as new and executed every time it is needed.
For a more detailed explanation of ContentPage and BackgroundTask, look here: Insight: ContentPage, BackgroundTask and JSONProcessingTask
3. MollyModule:
This is where all the binding and injection magic happens. Upon creating a Page in the Android app, one should have a clear idea what Molly app the Page is representing, and thus, create a String constant in the MollyModule in the form of:
public static String HOME_PAGE = "home:index";
which can be referred to as MollyModule.HOME_PAGE later on. Each page should return this value in its corresponding getName() method. Unfortunately, the best way is to hard code this in the getName() method of each Page.
After defining the constant, go to the method
@Override
protected void configure() { ... }
and add the desired binding:
bind(Page.class).annotatedWith(Names.named(HOME_PAGE)).to(HomePage.class);
It should be self-explained at this point. The above line of code means: “bind the class Page annotated with the string HOME_PAGE to its subclass HomePage”. All future references to the subclass of Page bound by the string MollyModule.HOME_PAGE should be made by a call to the static method:
getPageClass(MollyModule.HOME_PAGE)
in MyApplication or equivalent. This name will also be used in the actual HomePage to identify the app: the HomePage has a NetworkPollingTask which will download the list of available apps and visible to the user and display it (the first list view you see when opening the app). Clicking on one of these apps will open either the UnImplementedPage with the WebView or the actual page you created if you followed all the instructions given on this page.
Furthermore, you should notice that icons that are not found will be displayed as a question mark by default. When defining a binding for a Page, you should also define the binding for the icon of the page as well. There are 2 kinds of icons: app icon and app breadcrumbs icon, defined in 2 conventions: view_name + "_img" for app icons amd view_name + "_bc" for breadcrumbs. An example:
bind(Integer.class).annotatedWith(Names.named(PLACES_PAGE+"_img")).toInstance(R.drawable.places);
bind(Integer.class).annotatedWith(Names.named(PLACES_PAGE+"_bc")).toInstance(R.drawable.places_bc);
The above 2 lines bind the integer values R.drawable.places and R.drawable.places_bc annotated with "places:index_img" and "places:index_bc" respectively. These will be called later by the static method:
public static int getImgResourceId(String viewName)
{
return (injector.getInstance(Key.get(Integer.class, Names.named(viewName))));
}
So later on, whenever an icon is needed, its id can be recalled by an easy-to-remember name convention rather than the auto-generated integer value defined in R.java. This is used in the HomePage and ContentPage to find the correct app icons and app breadcrumbs icon.
4.AndroidManifest.xml
After all the hard work, be sure you avoid the ultimate frustration of seeing Molly Android crumbles with a fatal exception because the newly added Page is not found. The AndroidManifest is where all the Activities in an application are listed. This is also the place where the permissions are specified. An example AndroidManifest.xml looks like:
?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mollyproject.android"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.CALL_PHONE"/>
<application
android:name=".controller.MyApplication"
android:icon="@drawable/apple_touch_icon"
android:label="@string/app_name">
<activity android:name=".Splash" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".view.apps.home.HomePage"></activity>
</application>
</manifest>
A rough English translation would be:
-
The version of the app is 1.0
-
The minimum API level is 7 (Android 2.1 – update 1), i.e. devices must have at least Androido 2.1 – 1 to use this app
-
The app is permitted to access the Phone app to make phone calls
-
The first Activity it loads at startup is Splash (the splash screen)
-
Other than that it has an Activity available at org.mollyproject.android.view.apps.home.HomePage, all Pages must be added to the manifest manually here.
5.Summary:
So in conclusion, here are the things to remember when creating a new Page with accompanying BackgroundTasks to Molly Android:
-
The new page that deals directly with JSON should extend ContentPage, otherwise it should extend UnimplementedPage which hosts a WebView. Special cases like the BusPage and TrainPage in TransportPage extend Page because they are actually tabs.
-
Newly created views should only go to contentLayout in ContentPage, or more precisely whatever Layout specified in the getContentLayout() method (there are still some exception though)
-
Define the name of the equivalent Molly app as a String constant in MollyModule and return it in the Page.getName() method. This should be followed by the addition of the new binding.
-
All BackgroundTasks go to the refresh() method in a ContentPage.
-
doInBackground() in BackgroundTask must not touch the UI, this should only be done in the updateView() method, otherwise in some special case, in methods onPreExecute() and onPostExecute(). Note: updateView() lies in onPostExecute().
-
BackgroundTasks cannot be reused, must be redefined whenever needed
-
Remember to add your Page to the manifest. That is compulsory.