Archive for 'Android' Category

Let’s stop doing work inside RecyclerView/ListView Adapters

Below is a common use case for RecyclerView where it shows a list of items.

For example, the backend would return a list of data that include shipping type, location where it’s shipped from, and shipped date time; perhaps more information. Suppose the text color of each shipping type is an important business logic. This is a simplified example.

example

So what do you mean by “stop doing work”?

Inside ViewHolder class called from RecyclerViewAdapter.onBindViewHolder():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void bind(Shipping item) { // Shipping is a web service response model
    Context context = itemView.getContext();
 
    shippingTypeText.setText(item.getShipppingType());
 
    if (ShippingType.STANDARD.equals(item.getShipppingType())) {
        shippingTypeText.setTextColor(ContextCompat.getColor(context , R.color.shipping_type_standard));
    } else if (ShippingType.PRIORITY.equals(item.getShipppingType())) {
        shippingTypeText.setTextColor(ContextCompat.getColor(context , R.color.shipping_type_priority));
    } else if (ShippingType.SPECIAL.equals(item.getShipppingType())) {
         shippingTypeText.setTextColor(ContextCompat.getColor(context , R.color.shipping_type_special));
    } else {
         shippingTypeText.setTextColor(ContextCompat.getColor(context , R.color.shipping_type_default));
    }
 
    shippingFromText.setText(item.getShippingFrom().getCity() + ", " + item.getShippingFrom().getStateAbbr());
 
    // Formatting a string date time into display format
    dateTimeText.setText(DateTimeUtil.format(item.getShippedDateTime()));
}

In this example, date time parsing/formatting and non-UI/business logic are happening in ViewHolder or inside the Adapter. As user scrolls, bind() is being called many many times as RecyclerView/ListView recycles its items. Notice that the result of the work done here will not change.

What’s wrong?
  • Prevent possibility of scroll lag if you overdo work
  • Could increase complexity by mixing view and non-UI logic
  • I would definitely not want to see business logic in a ViewHolder.
  • Extra usage of memory
    • Avoid instantiating objects.
  • You can’t write unit test since this code is buried inside Adapter/ViewHolder.

  • [Continue reading…]

    Detect offline error in Retrofit 2

    Displaying ‘no internet connection’ error is quite a common requirement. The sample below demonstrates how to make Retrofit 2 throw ‘no internet connection’ exception.

    Essentially, you’ll create a class that implements Interceptor so that you can perform a network connectivity check before executing the request.

    ConnectivityInterceptor.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    public class ConnectivityInterceptor implements Interceptor {
     
        private Context mContext;
     
        public ConnectivityInterceptor(Context context) {
            mContext = context;
        }
     
        @Override
        public Response intercept(Chain chain) throws IOException {
            if (!NetworkUtil.isOnline(mContext)) {
                throw new NoConnectivityException();
            }
     
            Request.Builder builder = chain.request().newBuilder();
            return chain.proceed(builder.build());
        }
     
    }

    If the app knows there is no connectivity, stop and throw exception there. We will need a reference to Context object in order to check connectivity.




    NetworkUtil.java

    1
    2
    3
    4
    5
    
    public static boolean isOnline(Context context) {
    	ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    	NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
    	return (netInfo != null && netInfo.isConnected());
    }

    NoConnectivityException.java

    1
    2
    3
    4
    5
    6
    7
    8
    
    public class NoConnectivityException extends IOException {
     
        @Override
        public String getMessage() {
            return "No connectivity exception";
        }
     
    }

    Creating a custom Exception class allows us to catch it by Exception class type for error handling.

    When instantiating a Retrofit service,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(new ConnectivityInterceptor(context))
            .build();
     
    MyRetrofitService service = new Retrofit.Builder()
            .baseUrl("https://example.com)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(MyRetrofitService.class);

    In try-catch block for Retrofit web service failure,

    1
    2
    3
    
    if (exception instanceof NoConnectivityException) {
        // No internet connection
    }

    Include library based on product flavor

    Ex) I want to use debugging tool libraries such as Stetho only in dev product flavor.

    You can write a simple wrapper for each product flavor. This way, you can avoid including an unnecessary dependency to your Android apk.

    build.gradle

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    android {
        ...
        productFlavors {
            dev {
            }
            prod {
            }
        }
    }
     
    dependencies {
        ...
        devCompile 'com.facebook.stetho:stetho:1.4.1'
    }

    /app/src/dev/java/com/example/StethoWrapper.java:

    1
    2
    3
    4
    5
    6
    7
    
    public class StethoWrapper {
     
        public static void initializeWithDefaults(Application application) {
            Stetho.initializeWithDefaults(application);
        }
     
    }

    /app/src/prod/java/com/example/StethoWrapper.java:

    1
    2
    3
    4
    5
    6
    7
    
    public class StethoWrapper {
     
        public static void initializeWithDefaults(Application application) {
            // Not using Stetho for this product flavor
        }
     
    }

    /app/src/main/java/com/example/MyApplication.java

    1
    2
    3
    4
    5
    6
    7
    8
    
    public class MyApplication extends Application {
     
        public void onCreate() {
            super.onCreate();
            StethoWrapper.initializeWithDefaults(this);
        }
     
    }

    Although debugging tools like LeakCanary provide a disabled version of library to be used for release apk, I prefer to use the approach above to completely exclude the dependency from my apk.

    ViewPager with fixed background image

    Problem

    I want to have an image(such as a phone frame) fixed in its position while I swipe through contents in fragments on ViewPager. An example is “What’s New” or “Features” screen like gif animation below. Or maybe something fancier with your creativity.

    viewpager_gif

    Approach

    It’s simpler than it looks. On top of a regular implementation of fragments ViewPager in an Activity,

    • Make your Fragment layout background transparent
    • Wrap ViewPager and ImageView in FrameLayout within your Activity layout so that you can center the image underneath ViewPager
    Code Sample


    [Continue reading…]

    Implement your own keyboard for Android Wear app

    Problem – Key Input in Android Wear

    The question is, “how do you take key inputs on Android Wear app?”. I worked on a Wear app that needs to take number inputs. I needed a calculator-like number keyboard with keys for 0-9, -(negative sign), .(decimal), x(backspace), and C(clear).

    If you encounter a similar need, this article might help you get an idea for your solution.

    Approach – DialogFragment as Keyboard

    We can make use of a DialogFragment to represent a keyboard.

    keyboard_wear


    [Continue reading…]

    Pages:12>

    Categories