What Is the Singleton Pattern?
The Singleton Pattern is a software design pattern that guarantees a class has one instance only and a global point of access to it is provided by that class. Anytime multiple classes or clients request for that class, they get the same instance of the class. This Singleton class may be responsible for instantiating itself, or you can delegate the object creation to a factory class.
Let's use the example of a cellphone and its owner. A phone is typically owned by a single person, while a person can own many phones. Anytime one of these phones rings, the same owner picks it up.
Benefits of the Singleton Pattern
In a typical Android app, there are many objects for which we only need one global instance, whether you are using it directly or simply passing it to another class. Examples include caches, OkHttpClient
, HttpLoggingInterceptor
, Retrofit
, Gson
, SharedPreferences
, the repository class, etc. If we were to instantiate more than one of these types of objects, we'd run into problems like incorrect app behaviour, resource overuse, and other confusing results.
Implementation
It's quite easy to implement this pattern. The following code snippet shows how a Singleton is created.
public class Singleton { private static Singleton INSTANCE = null; // other instance variables can be here private Singleton() {}; public static Singleton getInstance() { if (INSTANCE == null) { INSTANCE = new Singleton(); } return(INSTANCE); } // other instance methods can follow }
In the code above, we have a static variable INSTANCE
to hold an instance of the class. We also made the constructor private because we want to enforce noninstantiability—the class can only instantiate itself. The method getInstance()
guarantees that the class is instantiated, if it has not been, and that it's returned to the caller.
Example: Creating a Single Instance of Retrofit
Retrofit is a popular library to connect a REST web service by translating the API into Java interfaces. To learn more about it, check out my tutorial here on Envato Tuts+.
In an Android app, you'll need a single global instance of a Retrofit object so that other parts of an app such as a UserProfileActivity
or SettingsActivity
can use it to execute a network request without the need to create an instance every single time we need it. Creating multiple instances would pollute our app with unused retrofit objects, thereby occupying unnecessary memory on an already memory-constrained mobile device.
import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class RetrofitClient { private static Retrofit retrofit = null; public static Retrofit getClient(String baseUrl) { if (retrofit==null) { retrofit = new Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create()) .build(); } return retrofit; } }
So anytime client A calls RetrofitClient.getClient()
, it creates the instance if it has not been created already, and then when client B calls this method, it checks if the Retrofit instance already exists. If so, it returns the instance to client B instead of creating a new one.
Dealing With Multithreading
In the Android system, you can spin off multiple threads to perform different tasks. These threads can end up executing the same code block simultaneously. In the case of the Singleton
class above, this could lead to the creation of multiple object instances, which violates the contract of a Singleton. So our Singleton code snippet method getInstance()
is not thread safe. We'll now look at ways to make it thread safe.
Synchronize the getInstance()
Method
One of the ways to make the singleton code thread safe is by making the method getInstance()
a synchronized one. Doing this only allows one thread to run the method at a time, forcing every other thread to be in a wait or blocked state.
public class Singleton { private static Singleton INSTANCE = null; // other instance variables can be here private Singleton() {}; public static synchronized Singleton getInstance() { if (INSTANCE == null) { INSTANCE = new Singleton(); } return(INSTANCE); } // other instance methods can follow }
This approach makes our code thread safe, but it is an expensive operation. In other words, this can slow down performance. So you have to investigate and see if the performance cost is worthwhile in your application.
Eagerly Create an Instance
Another approach to deal with multiple threads accessing the singleton is to create the Singleton instance immediately when the class is loaded or initialized (by the Android ClassLoader in the Dalvik VM). This makes the code thread safe. Then the object instance will already be available before any thread accesses the INSTANCE
variable.
public class Singleton { private static Singleton INSTANCE = new Singleton(); // other instance variables can be here private Singleton() {}; public static Singleton getInstance() { return(INSTANCE); } // other instance methods can follow }
A drawback to this approach is that you can end up creating an object that might never be used, thereby occupying unnecessary memory. So this approach should typically only be used if you are sure that the singleton will be accessed.
Bonus: Using Dagger 2
A dependency injection library such as Dagger can help you wire up your object dependencies and create singletons by using the @Singleton
annotation. This will ensure that the object is only initialized once throughout the application lifecycle.
@Module public class NetworkModule { @Provides @Singleton public Gson gson() { GsonBuilder gsonBuilder = new GsonBuilder(); return gsonBuilder.create(); } @Provides @Singleton public HttpLoggingInterceptor loggingInterceptor() { HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor( message -> Timber.i(message)); interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); return interceptor; } @Provides @Singleton public Cache cache(File cacheFile) { return new Cache(cacheFile, 10 * 1000 * 1000); //10MB Cache } @Provides @Singleton public File cacheFile(@ApplicationContext Context context) { return new File(context.getCacheDir(), "okhttp_cache"); } @Provides @Singleton public OkHttpClient okHttpClient(HttpLoggingInterceptor loggingInterceptor, Cache cache) { return new OkHttpClient.Builder() .addInterceptor(loggingInterceptor) .cache(cache) .build(); } @Provides @Singleton public Retrofit retrofit(OkHttpClient okHttpClient, Gson gson) { return new Retrofit.Builder() .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson)) .client(okHttpClient) .baseUrl("you/base/url") .build(); } }
In the code above, we create a single instance of Gson
, Cache
, File
, OkHttpClient
and finally Retrofit
types to be provided from the dependency graph generated by Dagger.
To learn more about Dagger 2, check out our tutorial here on Envato Tuts+.
Conclusion
In this short tutorial, you learned about the Singleton pattern in Android: what it is, the benefits of using it, how to implement it by writing your own, and some ways of dealing with multiple threads. I also showed you how to use a third-party library such as Dagger 2.
In the meantime, check out some of our other courses and tutorials on the Java language and Android app development!
-
Android SDKRxJava 2 for Android Apps: RxBinding and RxLifecycle
-
Android SDKPractical Concurrency on Android With HaMeR
-
AndroidEnsure High-Quality Android Code With Static Analysis Tools
-
Android SDKCreate an Intelligent App With Google Cloud Speech and Natural Language APIs
No comments:
Post a Comment