Saturday, January 25, 2014

Enabling Android App Sync settings from code

Ever wondered how to modify Android Sync settings under Settings -> Accounts -> Account from your app?
  1. Declare that you app uses android.permission.WRITE_SYNC_SETTINGS permission in Android manifest file.
  2. Use ContentResolver.setSyncAutomatically() method to check/uncheck the check box on the Account's Sync page.
And if you want to modify master Sync settings under Settings -> Data usage -> Auto-sync data, use ContentResolver.setMasterSyncAutomatically().

Synchronizing PreferenceScreen with SharedPreferences

PrefrenceFragment.addPreferencesFromResource() loads UI from a resource file and binds UI elements with SharedPreferences values from PreferenceManager.getDefaultSharedPreferences().
Suppose our screen has a CheckBoxPreference synchronized with a boolean value in SharedPreferences. And while we are displaying the preference screen, some service modifies the boolean value in SharedPreferences (of course by posting to UI thread for access serialization).  When this happens, the screen is not refreshed automatically and continues displaying the old value.
To keep the screen synchronized with SharedPreferences, we do the following:
  1. In OurSettingsFragment.onCreate() we add a listener to the shared preferences:
    PreferenceManager.getDefaultSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(listener)
  2. In OurSettingsFragment.onDestroy(), we remove the listener:
    PreferenceManager.getDefaultSharedPreferences(getActivity()).unregisterOnSharedPreferenceChangeListener(listener)
  3. In listener's onSharedPreferenceChanged(SharedPreferences prefs, String key) method: if the key parameter is the one that was assigned to our CheckBoxPreference in XML resource file
    • Find the preference by key:
      pref = (CheckBoxPreference)getPreferenceScreen().findPreference(key)
    • Compare the value stored in pref with the value stored in SharedPreferences and modify the pref variable if the values are different:
      final boolean prefValue = prefs.getBoolean(key, defaultValue);
      if(pref.IsChecked() != prefValue) {
          pref.setChecked(prefValue);
      }
      
When we modify the value displayed by CheckBoxPreference (or other preference UI element), the state of UI element on the screen changes and the value, that the UI element displays, is stored to the underlying SharedPreferences. But since the value being stored to SharedPreferences is the same as the value that was stored by our service, our listener is not called recursively and a possible infinite loop is avoided.  Other listeners that are listening property change events also do not get notified.

Saturday, January 11, 2014

Multitenancy and Android client: frustrating experience with GAE endpoints

When I read isolated technical documents published by Google, they all make sense.  When I start combining them together to build a working product, things often fall apart.  Here is one example.
Let's assume we have a GAE endpoint that requires authentication (more information on authenticated endpoints can be found in Testing authenticated GAE endpoints in Android Studio).  Let's also assume we want to build a multi-tenant application using Namespaces.  The goal is have a namespace per user as described in Creating namespaces on a per user basis.  The key point is to use user ID as the namespace name.  Let's try getting user ID first:
@Api(
    name = "testendpoint",
    namespace = @ApiNamespace(ownerDomain = Consts.API_OWNER_DOMAIN, 
        ownerName = Consts.API_OWNER_NAME, 
        packagePath = Consts.API_PACKAGE_PATH),
    scopes = { Consts.EMAIL_SCOPE },
    clientIds = { Consts.WEB_CLIENT_ID, Consts.ANDROID_CLIENT_ID, 
        Consts.ANDROID_CLIENT_ID_TEST },
    audiences = { Consts.ANDROID_AUDIENCE }
)
public class TestEndpoint {
    @ApiMethod(name = "getUserInfo", path = "getUserInfo")
    public UserInfo getUserInfo(User user) throws OAuthRequestException {
        if (user == null) {
            throw new OAuthRequestException("Method requires authentication");
        }
        return new UserInfo(user.getEmail(), user.getUserId());
    }
    // ...
}
Since authenticated @ApiMethods have user parameter, we do not need to use UserServiceFactory to get the current user.  All we need to do is to call user.getUserId().  We deploy our application to GAE, open the API explorer and test getUserInfo() method.  Everything works fine, both user.getEmail() and user.getUserId() return correct values.
Since we want to use our GAE application from an Android client, we want to verify that getUserInfo() also works when called from an Android application:
public void testUserInfo() throws Exception {
    final UserInfo ui = endpoint.getUserInfo().execute();
    Log.d(TAG, "userId: " + ui.getUserId());
    Log.d(TAG, "email: " + ui.getEmail());
}
Unfortunately it does not: getEmail() returns correct email address, but getUserId() returns null.  At this point we think that may be the user object that is passed to getUserInfo() method is created differently from the User object retrieved from UserServicesFactory.  To verify this assumption we add another method to TestEndpoint class:
    @ApiMethod(name = "getUserInfo2", path = "getUserInfo2")
    public UserInfo getUserInfo2(User user) throws OAuthRequestException {
        if (user == null) {
            throw new OAuthRequestException("Method requires authentication");
        }
        final User currentUser = 
            UserServiceFactory.getUserService().getCurrentUser();
        return new UserInfo(currentUser.getEmail(), currentUser.getUserId());
    }
We re-deploy our application to GAE, open the API explorer and test getUserInfo2(). Unfortunately, the method does not work even in the API explorer: the returned currentUser is null and our application crashes with NullPointerException. The same situation occurs when getUserInfo2() is called from an Android client:
public void testUserInfo2() throws Exception {
    final UserInfo ui = endpoint.getUserInfo2().execute();
    Log.d(TAG, "userId: " + ui.getUserId());
    Log.d(TAG, "email: " + ui.getEmail());
}
Setting a per-user namespace in a namespace filter also fails:
@Override
public void doFilter(ServletRequest request, 
    ServletResponse response, FilterChain chain) 
    throws IOException, ServletException {

    final UserService usrSvc = UserServiceFactory.getUserService();
    final User user = usrSvc.getCurrentUser();
    NamespaceManager.set(user.getUserId());
}
The returned user object is null and the next line fails with NullPointerException.
There is an open issue related to User.getUserId() returning null that has not been fixed for almost a year. Until this issue is addressed it will be impossible to create multi-tenant applications that utilize namespaces on per-user basis and work with Android clients. Hopefully the issue will be addressed soon. +Google Developers 

Sunday, January 5, 2014

GAE endpoints: do not use @Named("key") as a parameter of @ApiMethod

Suppose we have a class EntityEndpoint in Android Studio project called Project-AppEngine.  We define a method to retrieve an entity by its key (represented as a String):
@ApiMethod(name = "getEntity”)
public MyEntity getEntity(
        User user,
        @Named("key") String key
) throws OAuthRequestException {
   // ...
}
Then we generate client libraries:
  • Tools -> Google Cloud Tools -> Generate Client Libraries
Remember, we need to do this twice as described at the end of Minimalistic GAE Endpoints in Android Studio post. 
Then, in Project-endpoints/src/endpoint-src/java/com/example/package/entityendpoint/Entityendpoint.java, we'll have a problem.  The method setKey(String) will be defined twice:
  • The first method is for setting you API key
  • The second method is for setting the key parameter we defined earlier.
To avoid this problem, we change our method definition to something like this
@ApiMethod(name = "getEntity”)
public MyEntity getEntity(
        User user,
        @Named(“id”) String key
) throws OAuthRequestException {
   // ...
}
or like this
@ApiMethod(name = "getEntity”)
public MyEntity getEntity(
        User user,
        @Named(“id”) String id
) throws OAuthRequestException {
   // ...
}
Then the second generated client method will change its name to setId(String).
It would be useful if this was mentioned in the official documentation.