Wednesday, 8 February 2017

Android: Some background on Widgets


Background on the Widget Binding Process
The AppWidgetManager is a singleton object that runs when the System is started. This means that every instance of every launcher uses the same AppWidgetManager. What differentiates them is their AppWidgetHost and the RemoteViews they are currently holding. The AppWidgetManager basically keeps a list of all of the active hosts and the widgets they are holding. An AppWidgetHost is not a priveleged object. That is, any activity may have a single host. Thus, an entire application may be nothing but Widgets, if they so choose.
When you instantiate the Host, you must then add Views to it. So, basically it is a list of child Views with no mandatory parental bounds, except what your Activity gives it. First, you ask for an ID (via myHost.allocateAppWidgetId()). Then you use your Pick Widget Activity/Dialog. The Dialog returns the WidgetInfo. The View is retrieved when you ask the Host to create the View (via createView) with the WidgetInfo and the ID you asked for. It then asks the widget for its RemoteView.
Finally, you bind the widget by placing the View in your Activity as a Child. This is done via the addView() method of the ViewGroup that holds all of your Widgets.
The Process in Action 
First, you have to make sure you have this in your android manifest:
<uses-permission android:name="android.permission.BIND_APPWIDGET" />
Next, you have to create an AppWidgetHost (I extend my own for my launcher). The key to the Host is to keep a reference to the AppWidgetManager via AppWidgetManager.getInstance();.
AppWidgetHost myHost = new AppWidgetHost(context, SOME_NUMERICAL_CONSTANT_AS_AN_ID);
Now, get your ID:
myHost.allocateAppWidgetId()
The next step is done by whatever method you use to get the widget info. Most times it is returned via an Intent through onActivityResult. Now, all you really have to do is use the appInfo and create the view. The WidgetId is normally provided by the pick widget activity result.
AppWidgetProviderInfo withWidgetInfo 
        = AppWidgetManager.getInstance().getAppWidgetInfo(forWidgetId);
AppWidgetHostView hostView 
        = myWidgetHost.createView(myContext, forWidgetId, withWidgetInfo);
hostView.setAppWidget(forWidgetId, withWidgetInfo);
Now you just bind the View as a child to whatever you want to bind it to.
myViewGroup.addView(hostView);
Of course, you always have to consider where and how to place it, etc. Also, you have to make sure that your AppWidgetHost is listening before you start adding widgets.
myHost.startListening()
To Summarize
The Widget binding process spans many methods and steps, but all occurs through the AppWidgetHost. Because Widgets are coded outside of your namespace you don't have any control except for where you put them and how you size the View. Since they are ultimately code that runs in your space but outside of your control, the AppWidgetManager acts as a neutral mediator, while the AppWidgetHost serves as the facilitator on your app's behalf. Once this is understood, your task is simple. The steps above are all the required steps for any custom launcher (including my own).

Tuesday, 15 November 2016

Android: Toolbar not appearing on some devices (pre lollipop) using ActionDrawer (navigation drawer)


This is a common issue becoming less apparent when testing on Lollipop or later.
When using standard layouts (such as FrameLayoutRelativeLayout etc.) the default behavior is that child views get drawn in order they are added or inflated.
The fix would look similar to this:
<FrameLayout>
     <!-- Content will be drawn first - below the toolbar. -->
     <FrameLayout android:id="@+id/main_content" />
     <!-- Toolbar will be drawn next - above the content. -->
     <android.support.v7.widget.Toolbar android:id="@+id/toolbar" />
</FrameLayout>
However since Lollipop the android:elevation attribute can override this behavior. Since elevation defines precise position along the Z axis views with higher elevation values will be drawn above those with lower elevation values.

Monday, 17 October 2016

Android - Proper way to load data asynchronously from Content Providers


Whenever we have to access data from Content Providers, generally everyone tends to use ContentResolvers and query the appropriate content providers using its URI and get the results. This is how android shows examples of using the COntentResolver as well. However there is a general problem that I have observed across different development teams that people tend to copy android's examples or code from stackoverflow and in doing so, forget a very important thing - ContentResolver runs in the app's main thread! (also called as the UI thread). 
The UI thread is a bad place for lengthy operations like loading data. You never know how long data will take to load, especially if that data is sourced from a content provider or the network. Android 3.0 (Honeycomb) introduced the concept of Loaders and, in particular, the CursorLoader class that offloads the work of loading data on a thread, and keeps the data persistent during short term activity refresh events, such as an orientation change. 

Step 1: Using the Right Class Versions
Normally, we can get away with just using the default import statements of Android Studio. However, for loaders to work, we must ensure that we are using the correct versions of the classes. Here are the relevant import statements:
import android.support.v4.app.ListFragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.SimpleCursorAdapter;

public class NotificationsListFragment extends ListFragment implements         LoaderManager.LoaderCallbacks<Cursor> { // ... existing code // LoaderManager.LoaderCallbacks<Cursor> methods:     @Override     public Loader<Cursor> onCreateLoader(int id, Bundle args) {         // TBD     }     @Override     public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {         // TBD     }     @Override     public void onLoaderReset(Loader<Cursor> loader) {         // TBD     } }

// NotificationsListFragment class member variables private static final int NOTIFICATIONS_LIST_LOADER = 0; private SimpleCursorAdapter adapter; @Override public void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     String[] uiBindFrom = { TutListDatabase.COL_TITLE };     int[] uiBindTo = { R.id.title };     getLoaderManager().initLoader(NOTIFICATIONS_LIST_LOADER, null, this);     adapter = new SimpleCursorAdapter(             getActivity().getApplicationContext(), R.layout.list_item,             null, uiBindFrom, uiBindTo,             CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);     setListAdapter(adapter); }

As you can see, we've made the three changes. The Cursor object and the resulting query() call have been removed. In it’s place, we call the initLoader() method of the LoaderManager class. Although this method returns the loader object, there is no need for us to keep it around. Instead, the LoaderManager takes care of the details for us. All loaders are uniquely identified so the system knows if one must be newly created or not. We use the NOTIFICATIONS_LIST_LOADER constant to identify the single loader now in use. Finally, we changed the adapter to a class member variable and no cursor is passed to it yet by using a null value.
@Override public Loader<Cursor> onCreateLoader(int id, Bundle args) {     String[] projection = { NotificationsListDatabase.ID, NotificationsListDatabase.COL_TITLE };     CursorLoader cursorLoader = new CursorLoader(getActivity(),             NotificationsListProvider.CONTENT_URI, projection, null, null, null);     return cursorLoader; }
As you can see, it's fairly straightforward and really does look like the call to managedQuery(), but instead of a Cursor, we get a CursorLoader. And speaking of Cursors...
@Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {     adapter.swapCursor(cursor); }
The new swapCursor() method, introduced in API Level 11 and provided in the compatibility package, assigns the new Cursor but does not close the previous one. This allows the system to keep track of the Cursor and manage it for us, optimizing where appropriate.

@Override public void onLoaderReset(Loader<Cursor> loader) {     adapter.swapCursor(null); }
HINT: It can be achieved with a single functional line of code that will require a try-catch block.