Thursday, November 6, 2014

Android Studio/IntelliJ false error Object.getClass() shows as ambiguous method call

Tired of seeing this issue? Yes, it's terrible because it creates a lot noise for us to find the real errors. And we don't want to bend our code to remove the redness in the IDE with this hack - ((Object)this).getClass(). Solution: Link into the source code by clicking getClass() in your IDE. We should see the code below
    public final Class<?> getClass() {//Remove <?>
      return shadow$_klass_;
    }
Remove the <?> and refresh the project. The red lines should be gone.
    public final Class getClass() {//<?> removed
      return shadow$_klass_;
    }

Saturday, October 25, 2014

Nested Fragment with ChildFragmentManager lost state in rev20/rev21 of Android support library

After Android support library v4 rev 20 released, our team excitedly try to move on to it. But there was an annoying bug found - after screen rotation the inner nested fragment lost its state!

What we got was a ViewPager with several tabs in the nested fragment. All fragments setRetaininstanceState(true). On Android support lib v4 rev 19.+ everything worked fine. After we jumped on to rev 20, tabs don't display after rotation any more. This issue still exists in rev 21.0.0.

I started scratching my hair about this crazy issue until I found this was caused by the update of the android support library. If we revert back to use rev 19, everything went back fine. But we don't want to be stuck on rev19 forever as new version introduced many other important features.

After blood, sweat and tears, I finally found a work around if we really want to leap forward from support library v4 rev19. By diff of the source code of rev19 and rev20 I found what change results the problem. (the source files for different revisions can be found on your local drive under [AndroidSdkFolder]/extras/android/m2repository/com/android/support/support-v4). If you are interested, you can look into the source code. The change related to the bug is

after rev 20.0.0 there was new line at #1204 in android.support.v4.app.Fragment.java
  mChildFragmentManager = null;
added in method Fragment#initState().

As Fragment#initState() would be called internally by the lib every time the fragment is created regardless it's newly created or recreated, retain instance state or not. In the life cycles of a fragment, if the lib found mChildFragmentManager is not null it will dispatch events to it but as you can see mChildFragmentManager is reset null in rev20, so nothing would happen to the previous child fragment manager after rotation.

Solution:
Ideally, I hope android team could fix this issue ASAP. But before that there is a work around I found work. The idea is we retain the child fragment manager ourselves! To do so, we need to do the below trick to all nested fragments.

1. Create a field in the fragment to keep the child fragment manager created by the lib. As we set retain instance true, the reference held by the field will be kept after the rotation.

2. Before the lib dispatch the life cycle events of the the recreated fragment, we need to set its previous mChildFragmentManger retained by step 1 back to the new created fragment. The hook point is onAttachActivity(). As there is no public accessor we have to use reflection to set the mChildFragmentManger in the fragment. Caveats: reflection may cause problem for the future version because the field name may change.

3. We need to replace the destroyed activity attached to the fragment manager and existing fragments by the latest recreated activity.

Sample code:
public class NestingFragment extends Fragment {
    //...other codes

    private FragmentManager retainedChildFragmentManager;
    private FragmentHostCallback currentHost;
    private Class fragmentImplClass;
    private Field mHostField;

    {
        //Prepare the reflections to manage hiden fileds
        try {
            fragmentImplClass = Class.forName("android.support.v4.app.FragmentManagerImpl");
            mHostField = fragmentImplClass.getDeclaredField("mHost");
            mHostField.setAccessible(true);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("FragmentManagerImpl is renamed due to the " +
                    "change of Android SDK, this workaround doesn't work any more. " +
                    "See the issue at " +
                    "https://code.google.com/p/android/issues/detail?id=74222", e);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException("FragmentManagerImpl.mHost is found due to the " +
                    "change of Android SDK, this workaround doesn't work any more. " +
                    "See the issue at " +
                    "https://code.google.com/p/android/issues/detail?id=74222", e);
        }
    }

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

    protected FragmentManager childFragmentManager() {
        if (retainedChildFragmentManager == null) {
            retainedChildFragmentManager = getChildFragmentManager();
        }
        return retainedChildFragmentManager;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (retainedChildFragmentManager != null) {
            //restore the last retained child fragment manager to the new
            //created fragment
            try {
                //Copy the mHost(Activity) to retainedChildFragmentManager
                currentHost = (FragmentHostCallback) mHostField.get(getFragmentManager());

                Field childFMField = Fragment.class.getDeclaredField("mChildFragmentManager");
                childFMField.setAccessible(true);
                childFMField.set(this, retainedChildFragmentManager);

                refreshHosts(getFragmentManager());
            } catch (Exception e) {
                logger.warn(e.getMessage(), e);
            }
            //Refresh children fragment's hosts
        } else {
            //If the child fragment manager has not been retained yet, let it hold the internal
            //child fragment manager as early as possible. This can prevent child fragment
            //manager from missing to be set and then retained, which could happen when
            //OS kills activity and restarts it. In this case, the delegate fragment restored
            //but childFragmentManager() may not be called so mRetainedChildFragmentManager is
            //yet set. If the fragment is rotated, the state of child fragment manager will be
            //lost since mRetainedChildFragmentManager hasn't set to be retained by the OS.
            retainedChildFragmentManager = getChildFragmentManager();
        }
    }

    private void refreshHosts(FragmentManager fragmentManager) throws IllegalAccessException {
        if (fragmentManager != null) {
            replaceFragmentManagerHost(fragmentManager);
        }

        //replace host(activity) of fragments already added
        List frags = fragmentManager.getFragments();
        if (frags != null) {
            for (Fragment f : frags) {
                if (f != null) {
                    try {
                        //Copy the mHost(Activity) to retainedChildFragmentManager
                        Field mHostField = Fragment.class.getDeclaredField("mHost");
                        mHostField.setAccessible(true);
                        mHostField.set(f, currentHost);
                    } catch (Exception e) {
                        logger.warn(e.getMessage(), e);
                    }
                    if (f.getChildFragmentManager() != null) {
                        refreshHosts(f.getChildFragmentManager());
                    }
                }
            }
        }
    }

    //replace host(activity) of the fragment manager so that new fragments it creates will be attached
    //with correct activity
    private void replaceFragmentManagerHost(FragmentManager fragmentManager) throws IllegalAccessException {
        if (currentHost != null) {
            mHostField.set(fragmentManager, currentHost);
        }
    }

    //...other codes
}




Sunday, August 24, 2014

Count dex methods for Android depedencies

When you see the error below you encounter the frustrating limit of Android build tool that it allows maximally 64K methods included in one dex file.

dex java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536

Below is a script that helps count out the methods of all jar files which will be weaved into the dex file. Then you can try to exclude some unnecessary libs to dehydrate the apk. If it’s still too big you must consider to give up using some of the libs or go with multiple dex files.

totalmethods=0
for jar in `find . -type f -name "*.jar"`; do
echo $jar
dx --dex --output=temp.dex $jar 2> /dev/null
jarmethods=$(cat temp.dex | head -c 92 | tail -c 4 | hexdump -e '1/4 "%d\n"')
echo $jarmethods
totalmethods=$(($jarmethods+$totalmethods))
echo $totalmethods
done

How to run it:
1. Find the folder containing all the jars required by the APK. The path for me(my Android build tools is 20.0.0) is
Android Project/build/intermediates/pre-dexed/debug
2. Create a file and copy the scripts above into the file – say call it “countdex.sh"
3. In the same folder, run the command below
sh counted.sh 

Wednesday, January 15, 2014

Android Activity/Fragment life cycle analysis

Android life cycle is complex when activity comes with fragments. Though the document improves a lot, it still has not covered all scenarios. To have a better understanding of it, I made a simple app to print out all critical life cycles for both activity and nested fragments.

First of all, it is important to point out that when all activities of an app are killed the app process may still be running. As long as the process is running, all static variables will be valid. Once the process is killed by the OS all static variables will be lost. So be careful to use static variables.

To cover all possible scenarios that may impact your android app, I categorised them below
  1. New created: Fragment/Activity starts from nothing
  2. Rotate new created: Fragment/Activity restarts after rotation
  3. Home key pressed: Fragment/Activity is kicked to the background
  4. Back from background
with the combination of different cases:
  1. Fragment sets RetainInstance true/false
  2. App is killed in the background after home key pressed. To simulate the app is killed by the system in the background, you can tick the box under Settings->Developer options->Don't keep activities. In this case, the active activity will be killed even the app is sent to background by pressing home button.

Here is the simple project used for the analysis
https://github.com/kejunxia/AndroidLifeCycleAnalysis

Also here is a library to apply Android MVC pattern and make the lifecycle easier to be used as well
http://kejunxia.github.io/AndroidMvc/
https://github.com/kejunxia/AndroidMvc

Summary:
  • New created: almost the same for the four scenarios as below. Except the sequence of onCreateView and onWindowFocusChanged
  • Rotate new created: When Fragment get set RetainInstance true, fragment doesn't call onCreate and onDestroy. And the following fragment calls are invoked but bundle passed in are null
    • onCreateView
    • onViewCreated
    • onActivityCreated
    • onViewStateRestored
    Also note that onSaveInstanceState for both Activity and Fragment is called with non-null outState, no matter if the fragment is set RetainInstance true or false. Which means activities and fragments always save instance state during rotation.
  • Home button pressed(send app to background): onSaveInstanceState is called with outState always as the result above. When fragment is set RetainInstance true, the following calls won't be called:
    • Activity.onDestroy
    • Fragment.onDestroyView
    • Fragment.onDestroy
    • Fragment.onDetach
    • Activity.onDetachedFromWindow
  •  Back from background: This is a little complicated. I split it in to 2 cases.
    • App killed by system: this is simulated by turning on Don't Keep Activities in developer settings on the device. In this case the system is going to try restoring the previous state which should be stored by onSaveInstanceState(as mentioned it's always called). This is different from creating a new activity. In this case the system will do the things below:
      • Activity.onCreate will be called with the savedInstanceState to recover previous state, while if it's creating a new activity onCreate will recieve a null bundle.
      • Fragment.onAttach
      • Activity.onAttachFragment
      • Activity.onStart NOTE THAT this will be called after the fragment is attached while if it's new creating activity, onstart is before the fragment is attached.
      • Fragment.onCreate will be called with savedInstanceState like the activity.
      • Activity.onRestoreInstanceState is called which won't be called when creating a new activity
    • Back from background before killed:
      • no creation will occur
      • no savedInstanceState will occur

Life cycle outputs:

========================================================================
Activity:                            new created
Fragment retainInstance:   false
Kill Activity immediately:   false
ActivityCycle﹕ onCreate: bundle=null
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=null
FragmentCycle===>﹕ onCreateView: bundle=null
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=null
FragmentCycle===>﹕ onActivityCreated: bundle=null
FragmentCycle===>﹕ onViewStateRestored: bundle=null
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onPostCreate: bundle=null
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments`
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView

Activity:                            rotate new created
Fragment retainInstance:   false
Kill Activity immediately:   false
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop
ActivityCycle﹕ onDestroy
FragmentCycle===>﹕ onDestroyView
FragmentCycle===>﹕ onDestroy
FragmentCycle===>﹕ onDetach
ActivityCycle﹕ onDetachedFromWindow
ActivityCycle﹕ onCreate: bundle=Object
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=Object
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onCreateView: bundle=Object
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=Object
FragmentCycle===>﹕ onActivityCreated: bundle=Object
FragmentCycle===>﹕ onViewStateRestored: bundle=Object
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onRestoreInstanceState: bundle=Object
ActivityCycle﹕ onPostCreate: bundle=Object
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged

Activity:                           home button pressed
Fragment retainInstance:   false
Kill Activity immediately:   false
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop

Activity:                            back from background
Fragment retainInstance:   false
Kill Activity immediately:   false
ActivityCycle﹕ onRestart
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onWindowFocusChanged

========================================================================
Activity:                            new created
Fragment retainInstance:   true
Kill Activity immediately:   false

ActivityCycle﹕ onCreate: bundle=null
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=null
FragmentCycle===>﹕ onCreateView: bundle=null
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=null
FragmentCycle===>﹕ onActivityCreated: bundle=null
FragmentCycle===>﹕ onViewStateRestored: bundle=null
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onPostCreate: bundle=null
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView

Activity:                            rotate new created
Fragment retainInstance:   true
Kill Activity immediately:   false
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop
ActivityCycle﹕ onDestroy
FragmentCycle===>﹕ onDestroyView
FragmentCycle===>﹕ onDetach
ActivityCycle﹕ onDetachedFromWindow
ActivityCycle﹕ onCreate: bundle=Object
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onCreateView: bundle=null
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=null
FragmentCycle===>﹕ onActivityCreated: bundle=null
FragmentCycle===>﹕ onViewStateRestored: bundle=null
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onRestoreInstanceState: bundle=Object
ActivityCycle﹕ onPostCreate: bundle=Object
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged

Activity:                            home button pressed
Fragment retainInstance:   true
Kill Activity immediately:   
false
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop


Activity:                            back from background
Fragment retainInstance:   true
Kill Activity immediately:   
false
ActivityCycle﹕ onRestart
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onWindowFocusChanged

======================================================================
Activity:                            new created
Fragment retainInstance:   true
Kill Activity immediately:   true
ActivityCycle﹕ onCreate: bundle=null
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=null
FragmentCycle===>﹕ onCreateView: bundle=null
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=null
FragmentCycle===>﹕ onActivityCreated: bundle=null
FragmentCycle===>﹕ onViewStateRestored: bundle=null
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onPostCreate: bundle=null
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView


Activity:                            rotate new created
Fragment retainInstance:   true
Kill Activity immediately:   true
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop
ActivityCycle﹕ onDestroy
FragmentCycle===>﹕ onDestroyView
FragmentCycle===>﹕ onDetach
ActivityCycle﹕ onDetachedFromWindow
ActivityCycle﹕ onCreate: bundle=Object
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onCreateView: bundle=null
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=null
FragmentCycle===>﹕ onActivityCreated: bundle=null
FragmentCycle===>﹕ onViewStateRestored: bundle=null
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onRestoreInstanceState: bundle=Object
ActivityCycle﹕ onPostCreate: bundle=Object
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged


Activity:                            home button pressed
Fragment retainInstance:   true
Kill Activity immediately:   true
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop
ActivityCycle﹕ onDestroy
FragmentCycle===>﹕ onDestroyView
FragmentCycle===>﹕ onDestroy
FragmentCycle===>﹕ onDetach
ActivityCycle﹕ onDetachedFromWindow


Activity:                            back from background
Fragment retainInstance:   true
Kill Activity immediately:   true
ActivityCycle﹕ onCreate: bundle=Object
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=Object
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onCreateView: bundle=Object
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=Object
FragmentCycle===>﹕ onActivityCreated: bundle=Object
FragmentCycle===>﹕ onViewStateRestored: bundle=Object
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onRestoreInstanceState: bundle=Object
ActivityCycle﹕ onPostCreate: bundle=Object
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged


======================================================================
Activity:                            new created
Fragment retainInstance:   false
Kill Activity immediately:   true
ActivityCycle﹕ onCreate: bundle=null
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=null
FragmentCycle===>﹕ onCreateView: bundle=null
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=null
FragmentCycle===>﹕ onActivityCreated: bundle=null
FragmentCycle===>﹕ onViewStateRestored: bundle=null
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onPostCreate: bundle=null
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged


Activity:                            rotate new created
Fragment retainInstance:   false
Kill Activity immediately:   true
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop
ActivityCycle﹕ onDestroy
FragmentCycle===>﹕ onDestroyView
FragmentCycle===>﹕ onDestroy
FragmentCycle===>﹕ onDetach
ActivityCycle﹕ onDetachedFromWindow
ActivityCycle﹕ onCreate: bundle=Object
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=Object
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onCreateView: bundle=Object
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=Object
FragmentCycle===>﹕ onActivityCreated: bundle=Object
FragmentCycle===>﹕ onViewStateRestored: bundle=Object
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onRestoreInstanceState: bundle=Object
ActivityCycle﹕ onPostCreate: bundle=Object
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged

Activity:                            home button pressed
Fragment retainInstance:   false
Kill Activity immediately:   true
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop
ActivityCycle﹕ onDestroy
FragmentCycle===>﹕ onDestroyView
FragmentCycle===>﹕ onDestroy
FragmentCycle===>﹕ onDetach
ActivityCycle﹕ onDetachedFromWindow

Activity:                            back from background
Fragment retainInstance:   false
Kill Activity immediately:   true
ActivityCycle﹕ onCreate: bundle=Object
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=Object
ActivityCycle﹕ onCreateView          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onCreateView: bundle=Object
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=Object
FragmentCycle===>﹕ onActivityCreated: bundle=Object
FragmentCycle===>﹕ onViewStateRestored: bundle=Object
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onRestoreInstanceState: bundle=Object
ActivityCycle﹕ onPostCreate: bundle=Object
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged

Tuesday, January 7, 2014

Install Android Library on maven with gradle

If we need to hide the password or any other confidential information, we can move the info into gradle global properties. To do so, we put the info in the file at $GRADLE_HOME/gradle.properties. If it doesn't exist just create a new one.

Here is the content of file %GRADLE_HOME/gradle.properties.

signing.keyId=1C5E1752
signing.password=topsecret
signing.secretKeyRingFile=C:/Users/xxx/AppData/Roaming/gnupg/secring.gpg

artifactoryAndroidDev=http://artifactory.dev.cba/artifactory/android-dev/
artifactoryUsername=xxx
artifactoryPassword=topsecret
nexusSnapshotRepo=http://localhost:8081/nexus/content/repositories/snapshots/
nexusReleaseRepo=http://localhost:8081/nexus/content/repositories/releases/
nexusUsername=admin
nexusPassword=xxx

The we refer the info in the build.gradle file. Here is the gradle build file I used to install slidingmenu library.

buildscript {
    repositories {
        mavenCentral()
  maven {
            url 'http://localhost:8081/nexus/content/repositories/central/'
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.6.+'
    }
}
apply plugin: 'android-library'

repositories {
    maven {
        url 'http://localhost:8081/nexus/content/repositories/central/'
    }
}

dependencies {
    compile 'com.android.support:support-v4:18.0.+'
    compile 'com.actionbarsherlock:actionbarsherlock:4.4.0@aar'
}

android {
    compileSdkVersion 19
    buildToolsVersion "18.1.1"

    defaultConfig {
        minSdkVersion 7
        targetSdkVersion 19
    }

    sourceSets {
        main {
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']

            manifest.srcFile 'AndroidManifest.xml'
        }
    }

}

apply plugin: 'maven'
apply plugin: 'signing'
version = "1.3-release"
group = "com.jeremyfeinstein"
configurations {
    archives {
        extendsFrom configurations.default
    }
}

//Singing the library for release version.
signing {
    required { version.contains("release") && gradle.taskGraph.hasTask("uploadArchives") }
    sign configurations.archives
}

uploadArchives {
    configuration = configurations.archives
    repositories.mavenDeployer {
        beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }

//nexusReleaseRepo, nexusUsername and nexusPassword are defined in gradle global properties which are stored in $GRADLE_HOME/gradle.properties.
        repository(url: nexusReleaseRepo) {
            authentication(userName: nexusUsername, password: nexusPassword)
        }

        pom.project {
            name 'Android SlidingMenu Library'
            packaging 'aar'
            description 'Sliding menu library for Android applications'
            url 'https://github.com/jfeinstein10/SlidingMenu'

 //           scm {
 //               url 'scm:git@github.com:VandalSoftware/android-cache-lib.git'
 //               connection 'scm:git@github.com:VandalSoftware/android-cache-lib.git'
 //               developerConnection 'scm:git@github.com:VandalSoftware/android-cache-lib.git'
 //           }

            licenses {
                license {
                    name 'The Apache Software License, Version 2.0'
                    url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                    distribution 'repo'
                }
            }

            developers {
                developer {
                    id 'jfeinstein10'
                    name 'Jeremy Feinstein'
                    email 'jfeinstein10@gmail.com'
                }
            }
        }
    }
}

Sunday, December 8, 2013

Global properties in gradle

When we have multiple projects and we may want to refer global properties such as dependencies by all sub-projects.

For example we have a project structure like this
+ProjectX
\build.gradleX
\----SubProjectA
\--------build.gradleA
\----SubProjectB
\--------build.gradleB

So we have a root project called ProjectX and two sub projects called ProjectA and ProjectB. Each of them has their own build.gradle file for gradle build. By default, they all should be named as build.gradle. To distinguish them, we denote them as

  • build.gradleX
  • build.gradleA
  • build.gradleB

Say we want to use the same version of JUnit in all projects, so we need to use the same JUnit dependency and reference the same dependency across all projects.

In build.gradleX, we define a property
ext {
    lib = [
        junit: 'junit:junit:4.11'
    ]
}

In build.gradleA and build.gradleB we refer the dependency by
dependencies {
     compile lib.junit  //This references the property defined in build.gradleX
}

Improvement:
The mechanism of the above code is, the build.gradleX defines a property in root project(ProjectX) so in its subjects, lib.junit will be read inherited from the root project. But if we define ext.lib in PojectA as well like below then the lib.junit will use its local value. This is good if we want to override the value but if we don't know what happens behind the scene, mistake occurs.
in build.gradleA
ext {
    lib = [
        junit: 'junit:junit:3.0.0'
    ]
}

So, to refer the lib.ext we use
rootProject.lib.junit    //referencing the absolute root project 
or
parent.lib.junit         //referencing relative parent project which is the root here

Friday, August 9, 2013

Add new bank account to Google wallet / Android merchant account - Invalid input error for Bank code or Branch code

Recent when I tried to add new bank account into my payout settings linked to my Google Wallet for my Android apps, I got some wired errors. Here is the error and solution. Please note that I am in Australia. But it should work for other countries with similar

Error:



Solution: 

Please check out this info through the link below. It states how the bank code is composed.

Here is the solution for Australia developers:
In Australia, BSB number has 6 digits. Forget about swift code if you see something like this. So for developers from other countries you should  be able to figure error out with similar tricks.
  • Bank code = FIRST 3 DIGITS OF BSB
  • Branch code = SECOND 3 DIGITS OF BSB