Saturday, September 24, 2011

Undefined symbol "_OBJC_CLASS_$_GTMOAuth2ViewControllerTouch"

Google's developer documentation for GData Client for iPhone/ObjC seems outdated - I followed the instructions to build the static lib to the letter (Well.. I trust google!) and was stuck for a good few hours with the below error with OAuth classes missing from the static lib while doing release build:


Undefined symbols for architecture armv6:
  "_OBJC_CLASS_$_GTMOAuth2ViewControllerTouch", referenced from:
      objc-class-ref in xxx.o
ld: symbol(s) not found for architecture armv6
collect2: ld returned 1 exit status


Turns out the the "-DGDATA_INCLUDE_OAUTH2=1" C Flag  specified in the Removing Unneeded code section of the instruction is incorrect. It should be "-DGTM_INCLUDE_OAUTH2=1" for the OAuth classes to be linked correctly because thats what is coded inside the OAuth classes! Looks like someone updated the code but forgot to update the documentation.

I have logged an issue - hopefully someone will update the documentation so others don't run into the same issue.
(PS: Hey google, I need me lost hours back!)



Friday, January 14, 2011

Android WebView Proxy setting




Android webview is shaky at best - starting from missing root certificates, weird UI display issues like double text boxes and the most shaky of all - the connectivity itself. Apparently the webkit engine connectivity breaks very frequently with some(slow?) networks. Same issue exist in the andorid web browser application as well as webview reuses the same native browser implementation.
In some cases, the culprit is a slow proxy setup by the network provider by default. Bypassing the proxy could potentially fix that issue, but unfortunately there are no direct APIs to do that programatically, (WebView.enablePlatformNotifications(); require the user to manually enter proxy settings) so the below hack using reflection. The idea is to reset the proxyHost field in the android.net.http.RequestQueue instance within the Network object, so the WebKit engine finds it and sends the request accordingly. Code below.  (Warning: code below uses private APIs)

import android.content.Context;
import org.apache.http.HttpHost;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Utility class for setting WebKit proxy used by Android WebView
 *
 */
public class ProxySettings {

    /**
     * Override WebKit Proxy settings
     *
     * @param ctx Android ApplicationContext
     * @param host
     * @param port
     * @return  true if Proxy was successfully set
     */
    public static boolean setProxy(Context ctx, String host, int port) {
        boolean ret = false;
        try {
            Object requestQueueObject = getRequestQueue(ctx);
            if (requestQueueObject != null) {
                //Create Proxy config object and set it into request Q
                HttpHost httpHost = new HttpHost(host, port, "http");
                setDeclaredField(requestQueueObject, "mProxyHost", httpHost);
                ret = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ret;
    }

    public static void resetProxy(Context ctx) throws Exception {
        Object requestQueueObject = getRequestQueue(ctx);
        if (requestQueueObject != null) {
            setDeclaredField(requestQueueObject, "mProxyHost", null);
        }
    }

    public static Object getRequestQueue(Context ctx) throws Exception {
        Object ret = null;
        Class networkClass = Class.forName("android.webkit.Network");
        if (networkClass != null) {
            Object networkObj = invokeMethod(networkClass, "getInstance", new Object[]{ctx}, Context.class);
            if (networkObj != null) {
                ret = getDeclaredField(networkObj, "mRequestQueue");
            }
        }
        return ret;
    }

    private static Object getDeclaredField(Object obj, String name)
            throws SecurityException, NoSuchFieldException,
            IllegalArgumentException, IllegalAccessException {
        Field f = obj.getClass().getDeclaredField(name);
        f.setAccessible(true);
        Object out = f.get(obj);
        //System.out.println(obj.getClass().getName() + "." + name + " = "+ out);
        return out;
    }

    private static void setDeclaredField(Object obj, String name, Object value)
            throws SecurityException, NoSuchFieldException,
            IllegalArgumentException, IllegalAccessException {
        Field f = obj.getClass().getDeclaredField(name);
        f.setAccessible(true);
        f.set(obj, value);
    }

    private static Object invokeMethod(Object object, String methodName, Object[] params, Class... types) throws Exception {
        Object out = null;
        Class c = object instanceof Class ? (Class) object : object.getClass();
        if (types != null) {
            Method method = c.getMethod(methodName, types);
            out = method.invoke(object, params);
        } else {
            Method method = c.getMethod(methodName);
            out = method.invoke(object);
        }
        //System.out.println(object.getClass().getName() + "." + methodName + "() = "+ out);
        return out;
    }
}