Wednesday, January 20, 2016

Learnings from Nick Butcher's Plaid App - Part-1

Manifest.xml

  • Activity Alias

       Why to use activity alias and how to use it ?
       This is the best explanation so far i have seen :
       http://blog.danlew.net/2014/01/16/preserve-your-launchers-use-activity-alias/


<activity  
   android:name=".ui.HomeActivity"  
   android:label="@string/app_name"  
   android:theme="@style/Plaid.Home"  
   android:exported="true" />  
 <!-- use an alias in case we want to change the launch activity later without breaking  
    homescreen shortcuts. Note must be defined after the targetActivity -->  
 <activity-alias  
   android:name=".Launcher"  
   android:label="@string/app_name"  
   android:targetActivity=".ui.HomeActivity">  
   <intent-filter>  
     <action android:name="android.intent.action.MAIN" />  
     <category android:name="android.intent.category.LAUNCHER" />  
   </intent-filter>  
 </activity-alias>  

activity_home.xml

  • ViewStub

      I didnt know about ViewStub and its advantage. Please go through this article
      http://android-developers.blogspot.com/2009/03/android-layout-tricks-3-optimize-with.html
  • Progressbar tinting using the accent color

    android:indeterminateTint="?android:colorAccent" 
    android:indeterminateTintMode="src_in"
     <ProgressBar  
       android:id="@android:id/empty"  
       android:layout_width="wrap_content"  
       android:layout_height="wrap_content"  
       android:layout_gravity="center"  
       android:indeterminate="true"  
       android:indeterminateTint="?android:colorAccent"  
       android:indeterminateTintMode="src_in" />  

Wednesday, January 13, 2016

Guard your Android App with Proguard

This is how the module level build.gradle should look like 

buildTypes {
  release {
      minifyEnabled true  <----------- This is important   
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro' signingConfig signingConfigs.release } debug { // applicationIdSuffix '.debug' debuggable true } }

proguard-android.txt - [ SDK_PATH/tools/proguard ]
proguard-rules.pro -  Can be found in your Android Studio Project.

What are the typical issues that one can run into ?

  • If you have included third party libraries then please include their Proguard config as well in your project
Example : Lets say we have included firebase library then following rules are really good to have in
proguard-rules.pro
                  
-keep class com.firebase.** { *; }
-keep class org.apache.** { *; }
-keepnames class com.fasterxml.jackson.** { *; }
-keepnames class javax.servlet.** { *; }
-keepnames class org.ietf.jgss.** { *; }
-dontwarn org.w3c.dom.**
-dontwarn org.joda.time.**
-dontwarn org.shaded.apache.**
-dontwarn org.ietf.jgss.**


  • If your model classed implement Parcelables then we often see the error , Class not found while unmarshalling
 
-keepnames class * implements android.os.Parcelable { *; }
-keepclassmembers class * implements android.os.Parcelable { *; }


  • If you have used EventBus in the project , then you might run into "onEvent" method not found cause Proguard might be stripping it 
-keepclassmembers class ** {
    public void onEvent*(***);}

  • Json property not found and not marked ignorable , this is common error seen if you have used JsonSchemaToPojo jackson convertor and your model class doesnt contain all the properties in the schema cause you are interested only in few properties. Add a class level Annotation
@JsonIgnoreProperties(ignoreUnknown = true)
public class Match implements Parcelable {

    @JsonProperty("date")
    private String date;    @JsonProperty("matchid")
    private String matchid;    @JsonProperty("matchno")
    private String matchno;    @JsonProperty("result")
    private String result;}
  •  Error on some devices : couldn't find class 'com.google.android.gms.measurement.internal.zzz'  
if project level build.gradle contains
classpath 'com.google.gms:google-services:1.5.0-beta'
change that to 
classpath 'com.google.gms:google-services:1.5.0'

Tuesday, January 12, 2016

General Debugging tips

Trying to use Hierarchy Viewer on off the shelf android device and its not showing up any tree.

 Please checkout this :

 Just copy the ViewServer.java into your project.
 once you are done with that , check this sample activity 

 Debugging Webviews using chrome developer tools

   Since the android webviews are chromium based , you can actually debug webviews using more
   sophisticated chrome developer tools.
  • looking at DOM 
  • finding more about network requests and errors
  • even the console logging is available
Resources :


Wish to debug the lifecycle of a fragment 

    FragmentManager.enableDebugLogging(true) should do the trick.

    look at this post from awesome android developer " Chiu-ki-chan " 
    http://blog.sqisland.com/2014/06/navigationdrawer-creates-fragment-twice.html 
   

Monday, November 16, 2015

Whats minimum you can do to detect activity leaks in an Android App ?

Just enable the strict mode. Assuming you are coding at midnight and still want to make sure that there are no lingering activity leaks in your app , you think its too much to force a heap dump and analyze that in MAT / JHAT , well here comes the StrictMode to your rescue.

 if (BuildConfig.DEBUG) {  
   StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()  
       .detectAll()  
       .penaltyLog()  
       .build()); 
   StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()  
       .detectAll()  
       .penaltyLog()  
       .penaltyDeathOnNetwork()  
       .build());  
 }   

It totally came to me as a surprise but yeh its true and its awesome

Stack

com.cricket.material E/StrictMode: 
class com.cricket.material.cricket.Summary.LiveScoreSummaryDetail; 
instances=2; limit=1 
com.cricket.material E/StrictMode: 
android.os.StrictMode$InstanceCountViolation:  
class com.cricket.material.cricket.Summary.LiveScoreSummaryDetail;  
instances=2; limit=1
com.cricket.material E/StrictMode: 
at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)

Explanation :

Take a look at  StrictMode
/**
2324 If this is a instance count violation, the number of instances in memory,
2325 else -1.
2326 */
2327 public long numInstances = -1;

2188 Returns an object that is used to track instances of activites.
2189 The activity should store a reference to the tracker object in one of 
     its fields.
2190 * @hide
2191 */
2192 public static Object trackActivity(Object instance) {
2193       return new InstanceTracker(instance);
2194}

Note:

Please take this opportunity to understand other useful features of StrictMode
  • NetworkOnMainThread exception

Sunday, November 15, 2015

Don't override the default constructor of a fragment.

You should not override the default empty constructors of fragments, the reason for that is framework recreates fragments accross orientation change using the deafault constructor and any initialization that you do in non default constructor will be lost

But to really experiment about what happens , try to override the constructor and cause an orientation change , you will see that most likely you will hit a null pointer exception if you have passed an object to fragment constructor and tried to access the same after orientation change

This below is an ideal way to use fragment
  /**  
    * Use this factory method to create a new instance of  
    * this fragment using the provided parameters.  
    *  
    * @param param1 Parameter 1.  
    * @return A new instance of fragment SquadFragment.  
    */  
   // TODO: Rename and change types and number of parameters  
   public static SquadFragment newInstance(String param1) {  
     SquadFragment fragment = new SquadFragment();  
     Bundle args = new Bundle();  
     args.putString(ARG_PARAM1, param1);  
     fragment.setArguments(args);  
     return fragment;  
   }  
   
   public SquadFragment() {  
     // Required empty public constructor  
   }  
   
   @Override  
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     if (getArguments() != null) {  
       mParam1 = getArguments().getString(ARG_PARAM1);  
     }  
     mSquadAdapter = new SquadRecyclerViewAdapter(mParam1);  
   }  

Saturday, November 14, 2015

Android Services

There are actually two ways to implement a service 
  1. When your class MyService extends from Service 
  2. When your class MyService extends from IntentService

Rule of thumb:

No matter which service you implement , lifecycle methods of the service will be called on main thread aka UI Thread and this is where i have seen too much of confusion where newbies tend to think that all the methods in the service are called on background thread .
Because of this you should not be calling any methods that would essentially be calling any methods which would block your UI thread in lifecycle methods of service 

Things are not that BAD as you think as you might start to wonder about whats the point then...
  • If you implement #1 you have to start a worker thread in onStartCommand()
    • Once started always run in the background
    • We have to manage the life of service ie call Stopself() once the job is done
  @Override  
   public int onStartCommand(Intent intent, int flags, int startId) {  
     Log.i(TAG, "Service onStartCommand " + startId);  
     final int currentId = startId;  
     Runnable r = new Runnable() {  
       public void run() {  
       }  
     };  
     Thread t = new Thread(r);  
     t.start();  
     return Service.START_STICKY;  
   }  

  • If you implement #2 then you are saved from this and the onHandleIntent() method will be actually called on the background thread and you can actually call long running background task on this thread
    • Once the onHandleIntent() is finished the service is automatically stopped
    • Requests are queued if multiple requests are sent at the same time
 
 @Override  
 protected void onHandleIntent(Intent intent) {  
   Log.d(TAG, "#### " + Thread.currentThread().getName());  
   if (intent != null) {  
     final String action = intent.getAction();  
     }  
  }  
 

Wednesday, September 2, 2015

Espresso Tests and Contrib Dependency

I struggled a lot in getting the tests compiled as soon as i added contrib in build.gradle to test navigation drawer support.

Please use below dependency in build.gradle if you dont wish to suffer with contrib-2-2

androidTestCompile 'com.android.support.test:runner:0.3'
androidTestCompile 'com.android.support.test:rules:0.3'
androidTestCompile 'com.android.support:support-annotations:22.2.0'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2'){
    exclude group: 'com.android.support', module: 'appcompat'
    exclude group: 'com.android.support', module: 'support-v4'
    exclude module: 'recyclerview-v7'
}