Android – An Overview Of Application Security Best Practices

Hello Readers, CoolMonkTechie heartily welcomes you in this article (An Overview Of Application Security Best Practices).

In this article, we will learn about the best practices of Application Security in Android. By making our application more secure in android, we help preserve user trust and device integrity. This article explains about few best practices that have a significant, positive impact on our application’s security.

To understand the Application Security Best Practices, we cover the below topics as below :

  • Enforce secure communication with other applications
  • Provide the right permissions
  • Store data safely
  • Keep the services and related dependencies up-to-date

A famous quote about learning is :

“Being a student is easy. Learning requires actual work.”

So Let’s begin.

1. Enforce secure communication with other applications

When we safeguard the data that we want to exchange between our application and other applications, or between our application and a website, we improve our application’s stability and protect the data that we want to send and receive.

1.1 Use implicit intents and non-exported content providers

1.1.1 Show an application chooser

Use implicit intents to show application chooser that provides option to user to launch at least two possible applications on the device for the requested action. This allows users to transfer sensitive information to the application that they trust.

val intent = Intent(Intent.ACTION_SEND)
val possibleActivitiesList: List<ResolveInfo> =
        packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL)

// Verify that an activity in at least two applications on the user's device
// can handle the intent. Otherwise, start the intent only if an application
// on the user's device can handle the intent.
if (possibleActivitiesList.size > 1) {

    // Create intent to show chooser.
    // Title is something similar to "Share this photo with".

    val chooser = resources.getString(R.string.chooser_title).let { title ->
        Intent.createChooser(intent, title)
    }
    startActivity(chooser)
} else if (intent.resolveActivity(packageManager) != null) {
    startActivity(intent)
}

1.1.2 Apply signature-based permissions

Apply signature-based permissions while sharing data between two applications that is controlled by us. These permissions do not need user confirmation, but instead it checks that the applications accessing the data are signed using the same signing key. Hence offer more streamlined and secure user experience.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <permission android:name="my_custom_permission_name"
                android:protectionLevel="signature" />

1.1.3 Disallow access to our application’s content providers

Unless we intend to send data from our application to a different application that we don’t own, we should explicitly disallow other developers’ apps from accessing the ContentProvider objects that our application contains. This setting is particularly important if our application can be installed on devices running Android 4.1.1 (API level 16) or lower, as the android:exported attribute of the <provider> element is true by default on those versions of Android.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <application ... >
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.myapp.fileprovider"
            ...
            android:exported="false">
            <!-- Place child elements of <provider> here. -->
        </provider>
        ...
    </application>
</manifest>

1.2 Ask for credentials before showing sensitive information

When we are requesting the credentials so that we can access sensitive information or premium content in our application, ask for either a PIN/password/pattern or a biometric credential, such as using face recognition or fingerprint recognition.

1.3 Apply network security measures

Ensure network security with Security with HTTPS and SSL — For any kind of network communication we must use HTTPS (instead of plain http) with proper certificate implementation. This section describes how we can improve our application’s network security.

1.3.1 Use SSL traffic

If our application communicates with a web server that has a certificate issued by a well-known, trusted CA, the HTTPS request is very simple:

val url = URL("https://www.google.com")
val urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.connect()
urlConnection.inputStream.use {
    ...
}

1.3.2 Add a network security configuration

If our application uses new or custom CAs, we can declare our network’s security settings in a configuration file. This process allows us to create the configuration without modifying any application code.

To add a network security configuration file to our application, we can follow these steps:

  1. Declare the configuration in our application’s manifest:
<manifest ... >
    <application
        android:networkSecurityConfig="@xml/network_security_config"
        ... >
        <!-- Place child elements of <application> element here. -->
    </application>
</manifest>

2. We add an XML resource file, located at res/xml/network_security_config.xml.

Specify that all traffic to particular domains should use HTTPS by disabling clear-text:

<network-security-config>
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">secure.example.com</domain>
        ...
    </domain-config>
</network-security-config>

During the development process, we can use the <debug-overrides> element to explicitly allow user-installed certificates. This element overrides our application’s security-critical options during debugging and testing without affecting the application’s release configuration.

The following snippet shows how to define this element in our application’s network security configuration XML file:

<network-security-config>
    <debug-overrides>
        <trust-anchors>
            <certificates src="user" />
        </trust-anchors>
    </debug-overrides>
</network-security-config>

1.3.3 Create own trust manager

Our SSL checker shouldn’t accept every certificate. We may need to set up a trust manager and handle all SSL warnings that occur if one of the following conditions applies to our use case:

  • We’re communicating with a web server that has a certificate signed by a new or custom CA.
  • That CA isn’t trusted by the device we’re using.
  • You cannot use a network security configuration.

1.4 Use WebView objects carefully

Whenever possible, we load only allowlisted content in WebView objects. In other words, the WebView objects in our application shouldn’t allow users to navigate to sites that are outside of our control. In addition, we should never enable JavaScript interface support unless we completely control and trust the content in our application’s WebView objects.

1.4.1 Use HTML message channels

If our application must use JavaScript interface support on devices running Android 6.0 (API level 23) and higher, use HTML message channels instead of communicating between a website and your app, as shown in the following code snippet:

val myWebView: WebView = findViewById(R.id.webview)

// channel[0] and channel[1] represent the two ports.
// They are already entangled with each other and have been started.
val channel: Array<out WebMessagePort> = myWebView.createWebMessageChannel()

// Create handler for channel[0] to receive messages.
channel[0].setWebMessageCallback(object : WebMessagePort.WebMessageCallback() {

    override fun onMessage(port: WebMessagePort, message: WebMessage) {
        Log.d(TAG, "On port $port, received this message: $message")
    }
})

// Send a message from channel[1] to channel[0].
channel[1].postMessage(WebMessage("My secure message"))

2. Provide the right permissions

Application should request only the minimum number of permissions necessary to function properly.

2.1 Use intents to defer permissions

It should not add a permission to complete an action that could be completed in another application. Instead, we use an intent to defer the request to a different application that already has the necessary permission.

For example, If an application requires to create a contact to a contact application, it delegates the responsibility of creating the contact to a contacts application, which has already been granted the appropriate WRITE_CONTACTS permission.

// Delegates the responsibility of creating the contact to a contacts application,
// which has already been granted the appropriate WRITE_CONTACTS permission.
Intent(Intent.ACTION_INSERT).apply {
    type = ContactsContract.Contacts.CONTENT_TYPE
}.also { intent ->
    // Make sure that the user has a contacts application installed on their device.
    intent.resolveActivity(packageManager)?.run {
        startActivity(intent)
    }
}

In addition, if our application needs to perform file-based I/O – such as accessing storage or choosing a file – it doesn’t need special permissions because the system can complete the operations on our application’s behalf. Better still, after a user selects content at a particular URI, the calling application gets granted permission to the selected resource.

2.2 Share data securely across applications

We can follow these best practices in order to share our application’s content with other applications in a more secure manner:

  • Enforce read-only or write-only permissions as needed.
  • Provide clients one-time access to data by using the FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION flags.
  • When sharing data, we use “content://” URIs, not “file://” URIs. Instances of FileProvider do this for us.

The following code snippet shows how to use URI permission grant flags and content provider permissions to display an application’s PDF file in a separate PDF Viewer application:

// Create an Intent to launch a PDF viewer for a file owned by this application.
Intent(Intent.ACTION_VIEW).apply {
    data = Uri.parse("content://com.example/personal-info.pdf")

    // This flag gives the started application read access to the file.
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}.also { intent ->
    // Make sure that the user has a PDF viewer application installed on their device.
    intent.resolveActivity(packageManager)?.run {
        startActivity(intent)
    }
}

3. Store data safely

Although our application might require access to sensitive user information, our users will grant our application access to their data only if they trust that we’ll safeguard it properly.

3.1 Store private data within internal storage

We need to store all private user data within the device’s internal storage, which is sandboxed per application. Our application doesn’t need to request permission to view these files, and other applications cannot access the files. As an added security measure, when the user uninstalls an app, the device deletes all files that the app saved within internal storage.

We consider working with EncryptedFile objects if storing data is particularly sensitive or private. These objects are available from Security library instead of File objects.

For Example, one way to write data to storage demonstrates in the below code snippet:

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

// Create a file with this name, or replace an entire existing file
// that has the same name. Note that you cannot append to an existing file,
// and the file name cannot contain path separators.
val fileToWrite = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToWrite),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val fileContent = "MY SUPER-SECRET INFORMATION"
        .toByteArray(StandardCharsets.UTF_8)
encryptedFile.openFileOutput().apply {
    write(fileContent)
    flush()
    close()
}

Another example shows the inverse operation, reading data from storage:

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

val fileToRead = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToRead),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val inputStream = encryptedFile.openFileInput()
val byteArrayOutputStream = ByteArrayOutputStream()
var nextByte: Int = inputStream.read()
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte)
    nextByte = inputStream.read()
}

val plaintext: ByteArray = byteArrayOutputStream.toByteArray()

3.2 Store data in external storage based on use case

We consider external storage for large, non-sensitive files that are specific to our application, as well as files that our application shares with other applications. The specific APIs that we use depend on whether our application is designed to access app-specific files or access shared files.

3.2.1 Check availability of storage volume

When user interacts with a removable external storage device from the application, then he might remove the storage device while our app is trying to access it. We need to include logic to verify that the storage device is available.

3.2.2 Access application-specific files

If a file doesn’t contain private or sensitive information but provides value to the user only in our application, we store the file in an application-specific directory on external storage.

3.2.3 Access shared files

If our application needs to access or store a file that provides value to other applications, we can use one of the following APIs depending on our use case:

  • Media files: To store and access images, audio files, and videos that are shared between apps, use the Media Store API.
  • Other files: To store and access other types of shared files, including downloaded files, use the Storage Access Framework.

3.2.4 Check validity of data

If our application uses data from external storage, make sure that the contents of the data haven’t been corrupted or modified. Our application should also include logic to handle files that are no longer in a stable format.

We take an example of hash verifier in below code snippet:

val hash = calculateHash(stream)
// Store "expectedHash" in a secure location.
if (hash == expectedHash) {
    // Work with the content.
}

// Calculating the hash code can take quite a bit of time, so it shouldn't
// be done on the main thread.
suspend fun calculateHash(stream: InputStream): String {
    return withContext(Dispatchers.IO) {
        val digest = MessageDigest.getInstance("SHA-512")
        val digestStream = DigestInputStream(stream, digest)
        while (digestStream.read() != -1) {
            // The DigestInputStream does the work; nothing for us to do.
        }
        digest.digest().joinToString(":") { "%02x".format(it) }
    }
}

3.3 Store only non-sensitive data in cache files

To provide quicker access to non-sensitive application data, we store it in the device’s cache. For caches larger than 1 MB in size, we use getExternalCacheDir() otherwise, use getCacheDir(). Each method provides the File object that contains our application’s cached data.

Let’s take one example code snippet that shows how to cache a file that application recently downloaded:

val cacheFile = File(myDownloadedFileUri).let { fileToCache ->
    File(cacheDir.path, fileToCache.name)
}

If we use use getExternalCacheDir() to place our application’s cache within shared storage, the user might eject the media containing this storage while our application run. We should include logic to gracefully handle the cache miss that this user behavior causes.

3.4 Use SharedPreferences in private mode

When we are using getSharedPreferences() to create or access our application’s SharedPreferences objects, use MODE_PRIVATE. That way, only our application can access the information within the shared preferences file.

Moreover, EncryptedSharedPreferences should be used for more security which wraps the SharedPreferences class and automatically encrypts keys and values.

4. Keep services and dependencies up-to-date

Most applications use external libraries and device system information to complete specialized tasks. By keeping our app’s dependencies up to date, we make these points of communication more secure.

4.1 Check the Google Play services security provider

If our application uses Google Play services, make sure that it’s updated on the device where our application is installed. This check should be done asynchronously, off of the UI thread. If the device isn’t up-to-date, our application should trigger an authorization error.

4.2 Update all application dependencies

Before deploying our application, make sure that all libraries, SDKs, and other dependencies are up to date:

  • For first-party dependencies, such as the Android SDK, we use the updating tools found in Android Studio, such as the SDK Manager.
  • For third-party dependencies, we check the websites of the libraries that our app uses, and install any available updates and security patches.

That’s all about in this article.

Related Other Articles / Posts

Conclusion

In this article, we understood about the best practices of Application Security in Android. This article explained about few best practices that every mobile app developer must follow to secure the application from vulnerability. This helps us to develop the highly secure applications required to prevent valuable user information of our application and maintain the trust of our client.

Thanks for reading! I hope you enjoyed and learned about the best practices of Application Security concepts in Android. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Android – How To Implement Automatic Functions Memoization Technique In Kotlin?

Hello Readers, CoolMonkTechie heartily welcomes you in this article (How To Implement Automatic Functions Memoization Technique in Kotlin ?).

In this article, we will learn about how to implement Automatic Functions Memoization Technique in Kotlin. Memoization is a technique used to optimize the program-execution speed by caching the results of expensive function calls and reusing their ready values when they are required again. Although memoization causes an obvious trade-off between memory usage and computation time, often it’s crucial to provide the desired performance. This article explains about Automatic Functions Memoization Technique work flows in Kotlin.

To understand the Automatic Functions Memoization Technique in Kotlin, we cover the below topics as below :

  • Overview
  • How to Implement Technique?
  • How Technique Works?

A famous quote about learning is :

“That is what learning is. You suddenly understand something you’ve understood all your life, but in a new way.”

So Let’s begin.

Overview

We can understand about Memoization as:

  • A technique used to optimize the program-execution speed by caching the results of expensive function calls and reusing their ready values when they are required again.
  • An obvious trade-off between memory usage and computation time, often it’s crucial to provide the desired performance. Usually, we apply this pattern to computationally-expensive functions.
  • It can help to optimize recursive functions that call themselves multiple times with the same parameters values.

Memoization can easily be added internally to function implementation. However, in this article, we are going to create a general-purpose, reusable memoization mechanism that could be applied to any function.

How to Implement Technique?

In this section, We will understand how to implement it and What steps are required for implementation.

Step 1 – Declare a Memoizer class responsible for caching the results:

class Memoizer<P, R> private constructor() { 
   private val map = ConcurrentHashMap<P, R>() 
   private fun doMemoize(function: (P) -> R):
         (P) -> R = { param: P ->
            map.computeIfAbsent(param) { param: P ->
                function(param)
                    }
             }
   companion object { 
       fun <T, U> memoize(function: (T) -> U):
       (T) -> U =
                 Memoizer<T, U>().doMemoize(function)
     }
 } 

Step 2 – Provide a memoized() extension function for the (P) -> R function type:

fun <P, R> ((P) -> R).memoized(): (P) -> R = Memoizer.memoize<P, R>(this)

How Technique Works?

The memoize() function takes an instance of a one-argument function as its argument. The Memoizer class contains the ConcurrentHashmap<P, R> instance, which is used to cache the function’s return values. The map stores functions passed to memoize() as arguments as the keys, and it puts their return values as its values.

First, the memoize() function looks up the value for a specific parameter of the function passed as an argument. If the value is present in the map, it is returned. Otherwise, the function is executed and its result is returned by memoize() and put into the map. This is achieved using the handy inline fun <K, V> ConcurrentMap<K, V>.computeIfAbsent(key: K, defaultValue: () -> V): V extension function provided by the standard library.

Additionally, we can provide an extension function, memoized(), for the Function1 type, which will allow us to apply the memoize() function directly to the function references.

Note that the under the hood functions in Kotlin are compiled to the FunctionN interface instances in the Java bytecode, where N corresponds to the number of function arguments. We can declare an extension function for a function. For example, in order to add an extension function for a function taking two arguments, (P, Q) -> R, we need to define an extension as fun <P, Q, R> Function2<P, Q, R>.myExtension(): MyReturnType.

Now, take a look at how we can benefit from the memoized() function in action. Consider a function that computes the factorial of an integer recursively:

fun factorial(n: Int): Long = if (n == 1) n.toLong() else n * factorial(n - 1)

We can apply the memoized() extension function to enable caching of the results:

val cachedFactorial = ::factorial.memoized()
println(" Execution time: " + measureNanoTime { cachedFactorial(12) } + " ns")
println(" Execution time: " + measureNanoTime { cachedFactorial(13) } + " ns")

The above code snippet gives the following output on a standard computer:

Execution time: 1547274 ns
Execution time: 24690 ns

As we can see, even though the second computation requires a higher number of recursive calls of the factorial() function, it takes much less time than the first computation.

We can implement similar automatic memoization implementations for other functions that take more than one argument. In order to declare an extension function for a function taking N arguments, we’d have to implement an extension function for the FunctionN type.

That’s all about in this article.

Related Other Articles / Posts

Conclusion

In this article, we understood about how to implement Automatic Functions Memoization Technique in Kotlin. This article explained about Automatic Functions Memoization Technique work flows in Kotlin.

Thanks for reading! I hope you enjoyed and learned about Automatic Functions Memoization Technique concepts in Kotlin. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Android – How To Implement Either Monad Design Pattern in Kotlin ?

Hello Readers, CoolMonkTechie heartily welcomes you in this article (How To Implement Either Monad Design Pattern in Kotlin ?).

In this article, we will learn about how to implement Either Monad Design Pattern in Kotlin. The concept of Monad is one of the fundamental functional programming design patterns. We can understand a Monad as an encapsulation for a data type that adds a specific functionality to it or provides custom handlers for different states of the encapsulated object.

One of the most commonly used is a Maybe monad. The Maybe monad is supposed to provide information about the enclosed property presence. It can return an instance of the wrapped type whenever it’s available or nothing when it’s not. Java 8 introduced the Optional class, which is implementing the Maybe concept. It’s a great way to avoid operating on null values. This article explains about Either Monad Design Pattern work flows in Kotlin.

To understand the Either Monad Design Pattern in Kotlin, we cover the below topics as below :

  • Overview
  • Pattern Implementation Steps
  • How Pattern Works ?

A famous quote about learning is :

” The beautiful thing about learning is that nobody can take it away from you.”

So Let’s begin.

Overview

We can understand about Either Monad Design Pattern as:

  • A fundamental functional programming design patterns and,
  • Consider a Monad as an encapsulation for a data type that adds a specific functionality or provides custom handlers for different states of the encapsulated object.

However, apart from having the information about the unavailable state, we would often like to be able to provide some additional information. For example, if the server returns an empty response, it would be useful to get an error code or a message instead of the null or an empty response string. This is a scenario for another type of Monad, usually called Either, which we are going to implement in this article.

Pattern Implementation Steps

In this section, We will understand how to implement it and What steps are required for implementation.

Step 1 – Declare Either as a sealed class

sealed class Either<out E, out V>

Step 2 – Add two subclasses of Either, representing Error and Value:

sealed class Either<out L, out R> {
     data class Left<out L>(val left: L) : Either<L, Nothing>()
     data class Right<out R>(val right: R) : Either<Nothing, R>()
 }
 

Step 3 – Add factory functions to conveniently instantiate Either:

sealed class Either<out L, out R> {
     data class Left<out L>(val left: L) : Either<L, Nothing>()
     data class Right<out R>(val right: R) : Either<Nothing, R>()

     companion object {
        fun <R> right(value: R): Either<Nothing, R> = Either.Right(value) 
        fun <L> left(value: L): Either<L, Nothing> = Either.Left(value)
     }
 }

How Pattern Works ?

In order to make use of the class Either and benefit from the Either.right() and Either.left() methods, we can implement a getEither() function that will try to perform some operation passed to it as a parameter. If the operation succeeds, it is going to return the Either.Right instance holding the result of the operation, otherwise, it is going to return Either.Left, holding a thrown exception instance:

fun<V> getEither(action: () -> V): Either<Exception, V> =
         try { Either.right(action()) } catch (e: Exception) { Either.left(e) }
 

By convention, we can use the Either.Right type to provide a default value and Either.Left to handle any possible edge cases.

One essential functional programming feature the Either Monad can provide is the ability to apply functions to its values. We can simply extend the Either class with the fold() function, which can take two functions as the parameters. The first function should be applied to the Either.Left type and the second should be applied to Either.Right:

sealed class Either<out L, out R> {
     data class Left<out L>(val left: L) : Either<L, Nothing>()
     data class Right<out R>(val right: R) : Either<Nothing, R>()

 fun <T> fold(leftOp: (L) -> T, rightOp: (R) -> T): T = when (this) {
         is Left -> leftOp(this.left)
         is Right -> rightOp(this.right)
     }
 //…
 }
 

The fold() function will return a value from either the leftOp or rightOp function, whichever is used. The usage of the fold() function can be illustrated with a server-request parsing example.

Suppose we have the following types declared:

data class Response(val json: JsonObject)
data class ErrorResponse(val code: Int, val message: String)

And we have also a function responsible for delivering a backend response:

fun someGetRequest(): Either<ErrorResponse, Response> = //..
  

We can use the fold() function to handle the returned value in the right way:

someGetRequest().fold({
     showErrorInfo(it.message)
 }, {
     parseAndDisplayResults(it.json)
 })

We can also extend the Either class with other useful functions, like the ones available in the standard library for data-processing operations—mapfilter, and exists.

That’s all about in this article.

Related Other Articles / Posts

Conclusion

In this article, we understood about how to implement Either Monad Design Pattern in Kotlin. This article explained about Either Monad Design Pattern work flows in Kotlin.

Thanks for reading! I hope you enjoyed and learned about Either Monad Design Pattern concepts in Kotlin. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Android – How To Schedule Tasks With WorkManager ?

Hello Readers, CoolMonkTechie heartily welcomes you in this article (How To Schedule Tasks With WorkManager ?).

In this article, we will learn about how to schedule tasks with WorkManager. WorkManager is an API that makes it easy to schedule deferrable, asynchronous tasks that are expected to run even if the app exits or the device restarts. The WorkManager API is a suitable and recommended replacement for all previous Android background scheduling APIs, including FirebaseJobDispatcher, GcmNetworkManager, and Job Scheduler. WorkManager incorporates the features of its predecessors in a modern, consistent API that works back to API level 14 while also being conscious of battery life. This article explains about WorkManager workflow and advantages in Android application.

To understand the WorkManager in Android, we cover the below topics as below :

  • What is WorkManager?
  • Why use WorkManager ?
  • Features of WorkManager
  • When to use WorkManager?
  • How WorkManager works?
  • WorkManager implementation demo application to Schedule Tasks
  • Demo application output

A famous quote about learning is :

There is no end to education. It is not that you read a book, pass an examination, and finish with education. The whole of life, from the moment you are born to the moment you die, is a process of learning.

So Let’s begin.

What is WorkManager ?

WorkManager is a background processing library which is used to execute background tasks which should run in a guaranteed way but not necessarily immediately.

WorkManager is a task scheduler that makes it easy to specify the asynchronous task easily and when they should run. The WorkManager API helps create the task and hand it to the Work Manager to run immediately or at an appropriate time as mentioned.

With WorkManager, we can enqueue our background processing even when the app is not running and the device is rebooted for some reason. WorkManager also lets us define constraints necessary to run the task e.g. network availability before starting the background task.

For example, we might point our app to download new resources from the network from time to time and now the downloading is a task and we can set up this task to run at an appropriate time depending on the availability of the WIFI network or when the device is charging. So this way we can schedule a task using WorkManager.

WorkManager is a part of Android Jetpack (a suite of libraries to guide developers to write quality apps) and is one of the Android Architecture Components (collection of components that help developers design robust, testable, and easily maintainable apps).

Source : Android Developers – WorkManager Criteria

If our app targets Android 10 (API level 29) or above, our FirebaseJobDispatcher and GcmNetworkManager API calls will no longer work on devices running Android Marshmallow (6.0) and above.

Why use WorkManager ?

Since Marshmallow, The Android development team is continuously working on battery optimizations. After that team introduced Doze mode. Then in Oreo imposed various kind of limitation on performing background jobs. Before WorkManager, we use various job scheduler for performing background task, such as Firebase JobDispatcher, Job Scheduler and Alarm Manager + Broadcast receivers. So for the developer perspective, it is difficult to choose which scheduler should use and which one is good. So the Work Manager handles these kinds of stuff. We have to pass the task to the WorkManager and It uses all this Firebase Job Dispatcher, Alarm Manager + Broadcast Receivers, Job Scheduler to perform the background task depending on the requirement.

Features of WorkManager

In addition to providing a simpler and consistent API, WorkManager has a number of other key benefits, including:

Work Constraints

Declaratively define the optimal conditions for our work to run using Work Constraints. (For example, run only when the device is Wi-Fi, when the device idle, or when it has sufficient storage space, etc.)

Robust Scheduling

WorkManager allows us to schedule work to run one- time or repeatedly using flexible scheduling windows. Work can be tagged and named as well, allowing us to schedule unique, replaceable work and monitor or cancel groups of work together. Scheduled work is stored in an internally managed SQLite database and WorkManager takes care of ensuring that this work persists and is rescheduled across device reboots. In addition, WorkManager adheres to power-saving features and best practices like Doze mode, so we don’t have to worry about it.

Flexible Retry Policy

WorkManager offers flexible retry policies, including a configurable exponential backoff policy, if work fails.

Work Chaining

For complex related work, chain individual work tasks together using a fluent, natural, interface that allows us to control which pieces run sequentially and which run in parallel. For each work task, we can define input and output data for that work. When chaining work together, WorkManager automatically passes output data from one work task to the next.

Built-In Threading Interoperability

WorkManager integrates seamlessly with RxJava and Coroutines and provides the flexibility to plug in our own asynchronous APIs.

When to use WorkManager?

WorkManager handles background work that needs to run when various constraints are met, regardless of whether the application process is alive or not. Background work can be started when the app is in the background, when the app is in the foreground, or when the app starts in the foreground but goes to the background. Regardless of what the application is doing, background work should continue to execute, or be restarted if Android kills its process.

A common confusion about WorkManager is that it’s for tasks that needs to be run in a “background” thread but don’t need to survive process death. This is not the case. There are other solutions for this use case like Kotlin’s coroutines, ThreadPools, or libraries like RxJava. There are many different situations in which we need to run background work, and therefore different solutions for running background work.

WorkManager can be a perfect background processing library to use in android when our task:

  • Does not need to run at a specific time
  • Can be deferred to be executed
  • Is guaranteed to run even after the app is killed or device is restarted
  • Has to meet constraints like battery supply or network availability before execution

The simplest example can be when our app needs to upload a large chunk of user data to the server. This particular use case meets the criteria we mentioned above to choose WorkManager because:

  • Results need not be reflected immediately in our Android app
  • Data needs to be uploaded even when the upload begins and the user kills the app to work on some other app, and
  • The network needs to be available in order to upload data on the server.

How WorkManager Works ?

In this section, we will understand the class and concept of WorkManager. Let’s understand what are various base classes that are used for Job Scheduling.

Worker

Work is defined using the Worker class. It specifies what task to perform. The WorkManager API include an abstract Worker class and we need to extends this class and perform the work.

WorkRequest

WorkRequest represents an individual task that is to be performed. Now this WorkRequest, we can add values details for the work. Such as constraint or we can also add data while creating the request.

WorkRequest can be of to type :

  • OneTimeWorkRequest– That means we requesting for non-repetitive work.
  • PeriodicWorkRequest– This class is used for creating a request for repetitive work.

WorkManager

The WorkManager class in enqueues and manages all the work request. We pass work request object to this WorkManager to enqueue the task.

WorkInfo

WorkInfo contains the information about a particular task, The WorkManager provides LiveData for each of the work request objects, We can observe this and get the current status of the task.

WorkManager Implementation Demo Application to Schedule Tasks

In this section, we will discuss the below steps for implement WorkManager to schedule task with authentic android demo application :

  • Add WorkManager dependency in app/buid.gradle file
  • Create Layout
  • Add a base class of Worker
  • Create WorkRequest
  • Enqueue the request with WorkManager
  • Fetch the particular task status

1. Add WorkManager dependency in app/buid.gradle file

For using WorkManager we have to add dependency in app/build.gradle file. So let’s open the app build.gradle file and add below lines.

implementation 'androidx.work:work-runtime-ktx:2.5.0'

The source code of app/buid.gradle file is

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "com.coolmonktechie.android.workmanagerdemo"
        minSdkVersion 23
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'androidx.work:work-runtime-ktx:2.5.0'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

2. Create Layout

In this step, we will create a layout. This layout will contain TextView and Button. After that, we will set onClickListener() and this event will enqueue the WorkRequest to WorkManager and shows the status on TextView.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tvStatus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:gravity="center"
        android:text="Result display here"
        android:textSize="18sp"
        app:layout_constraintBottom_toTopOf="@+id/btnSend"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed" />

    <Button
        android:id="@+id/btnSend"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:background="@color/accent"
        android:paddingLeft="8dp"
        android:paddingRight="8dp"
        android:text="Send Notification without Constraints"
        android:textAllCaps="false"
        android:textColor="#fff"

        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvStatus" />

    <Button
        android:id="@+id/buttonStorageNotLow"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:background="@color/accent"
        android:text="Send Notification with requiresStorageNotLow()"
        android:textAllCaps="false"
        android:textColor="#fff"
        app:layout_constraintBottom_toTopOf="@+id/buttonBatteryNotLow"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnSend" />

    <Button
        android:id="@+id/buttonBatteryNotLow"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:background="@color/accent"
        android:text="Send Notification with requiresBatteryNotLow()"
        android:textAllCaps="false"
        android:textColor="#fff"
        app:layout_constraintBottom_toTopOf="@+id/buttonRequiresCharging"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/buttonStorageNotLow" />

    <Button
        android:id="@+id/buttonRequiresCharging"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:background="@color/accent"
        android:text="Send Notification with requiresCharging()"
        android:textAllCaps="false"
        android:textColor="#fff"
        app:layout_constraintBottom_toTopOf="@+id/buttonDeviceIdle"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/buttonBatteryNotLow" />

    <Button
        android:id="@+id/buttonDeviceIdle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:background="@color/accent"
        android:text="Send Notification with requiresDeviceIdle()"
        android:textAllCaps="false"
        android:textColor="#fff"
        app:layout_constraintBottom_toTopOf="@+id/buttonNetworkType"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/buttonRequiresCharging" />

    <Button
        android:id="@+id/buttonNetworkType"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="32dp"
        android:background="@color/accent"
        android:text="Send Notification with getRequiredNetworkType()"
        android:textAllCaps="false"
        android:textColor="#fff"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/buttonDeviceIdle" />

</androidx.constraintlayout.widget.ConstraintLayout>

3. Add a base class of Worker

In this step, we create a base class of Worker class and override un-implemented methods and super constructor.

NotificationWorker.kt

package com.coolmonktechie.android.workmanagerdemo

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.work.Data
import androidx.work.Worker
import androidx.work.WorkerParameters

class NotificationWorker(context: Context, workerParams: WorkerParameters) :
    Worker(context, workerParams) {
    override fun doWork(): Result {
        val taskData = inputData
        val taskDataString = taskData.getString(MainActivity.MESSAGE_STATUS)
        showNotification("WorkManager", "Message has been Sent")
        val outputData = Data.Builder().putString(WORK_RESULT, "Jobs Finished").build()
        return Result.success(outputData)
    }

    private fun showNotification(task: String, desc: String) {
        val manager =
            applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val channelId = "task_channel"
        val channelName = "task_name"
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel =
                NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT)
            manager.createNotificationChannel(channel)
        }
        val builder = NotificationCompat.Builder(applicationContext, channelId)
            .setContentTitle(task)
            .setContentText(desc)
            .setSmallIcon(R.mipmap.ic_launcher)
        manager.notify(1, builder.build())
    }

    companion object {
        private const val WORK_RESULT = "work_result"
    }
}

4. Create WorkRequest

Let’s move to MainActivity and create a WorkRequest to execute the work that we just created. Now first we will create WorkManager. This work manager will enqueue and manage our work request.

var mWorkManager:WorkManager = WorkManager.getInstance()

Now we will create OneTimeWorkRequest, because we want to create a task that will be executed just once.

var mRequest: OneTimeWorkRequest = OneTimeWorkRequest.Builder(NotificationWorker::class.java).build()

Using this code, we built work request that will be executed one time only.

5. Enqueue the request with WorkManager

In this step, we discuss onClick() of the button. we will enqueue this request using the WorkManager. So that’s all we need to do.

mWorkManager!!.enqueue(mRequest!!)

6. Fetch the particular task status

In this steps, we will fetch some information about this particular task and display it on tvStatus TextView. We will do that using WorkInfo class. The work manager provides LiveData for each of the work request objects, We can observe this and get the current status of the task.

mWorkManager!!.getWorkInfoByIdLiveData(mRequest!!.id).observe(this, { workInfo ->
        if (workInfo != null) {
            val state = workInfo.state
            tvStatus!!.append(
                """
$state

""".trimIndent()
            )
        }
    })

Finally, the full source code of MainActivity.kt looks like this:

package com.coolmonktechie.android.workmanagerdemo

import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.work.Constraints
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.sunilmishra.android.workmanagerdemo.NotificationWorker

class MainActivity : AppCompatActivity(), View.OnClickListener {
    var tvStatus: TextView? = null
    var btnSend: Button? = null
    var btnStorageNotLow: Button? = null
    var btnBatteryNotLow: Button? = null
    var btnRequiresCharging: Button? = null
    var btnDeviceIdle: Button? = null
    var btnNetworkType: Button? = null
    var mRequest: OneTimeWorkRequest? = null
    var mWorkManager: WorkManager? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initViews()
        tvStatus = findViewById(R.id.tvStatus)
        btnSend = findViewById(R.id.btnSend)
        mWorkManager = WorkManager.getInstance()
    }

    private fun initViews() {
        tvStatus = findViewById(R.id.tvStatus)
        btnSend = findViewById(R.id.btnSend)
        btnStorageNotLow = findViewById(R.id.buttonStorageNotLow)
        btnBatteryNotLow = findViewById(R.id.buttonBatteryNotLow)
        btnRequiresCharging = findViewById(R.id.buttonRequiresCharging)
        btnDeviceIdle = findViewById(R.id.buttonDeviceIdle)
        btnNetworkType = findViewById(R.id.buttonNetworkType)
        btnSend!!.setOnClickListener(this)
        btnStorageNotLow!!.setOnClickListener(this)
        btnBatteryNotLow!!.setOnClickListener(this)
        btnRequiresCharging!!.setOnClickListener(this)
        btnDeviceIdle!!.setOnClickListener(this)
        btnNetworkType!!.setOnClickListener(this)
    }

    override fun onClick(v: View) {
        tvStatus!!.text = ""
        val mConstraints: Constraints
        when (v.id) {
            R.id.btnSend -> mRequest =
                OneTimeWorkRequest.Builder(NotificationWorker::class.java).build()
            R.id.buttonStorageNotLow -> {
                /**
                 * Constraints
                 * If TRUE task execute only when storage's is not low
                 */
                mConstraints = Constraints.Builder().setRequiresStorageNotLow(true).build()
                /**
                 * OneTimeWorkRequest with requiresStorageNotLow Constraints
                 */
                mRequest = OneTimeWorkRequest.Builder(NotificationWorker::class.java)
                    .setConstraints(mConstraints).build()
            }
            R.id.buttonBatteryNotLow -> {
                /**
                 * Constraints
                 * If TRUE task execute only when battery isn't low
                 */
                mConstraints = Constraints.Builder().setRequiresBatteryNotLow(true).build()
                /**
                 * OneTimeWorkRequest with requiresBatteryNotLow Constraints
                 */
                mRequest = OneTimeWorkRequest.Builder(NotificationWorker::class.java)
                    .setConstraints(mConstraints).build()
            }
            R.id.buttonRequiresCharging -> {
                /**
                 * Constraints
                 * If TRUE while the device is charging
                 */
                mConstraints = Constraints.Builder().setRequiresCharging(true).build()
                /**
                 * OneTimeWorkRequest with requiresCharging Constraints
                 */
                mRequest = OneTimeWorkRequest.Builder(NotificationWorker::class.java)
                    .setConstraints(mConstraints).build()
            }
            R.id.buttonDeviceIdle -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                /**
                 * Constraints
                 * If TRUE while the  device is idle
                 */
                mConstraints = Constraints.Builder().setRequiresDeviceIdle(true).build()
                /**
                 * OneTimeWorkRequest with requiresDeviceIdle Constraints
                 */
                mRequest = OneTimeWorkRequest.Builder(NotificationWorker::class.java)
                    .setConstraints(mConstraints).build()
            }
            R.id.buttonNetworkType -> {
                /**
                 * Constraints
                 * Network type is conneted
                 */
                mConstraints =
                    Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
                /**
                 * OneTimeWorkRequest with requiredNetworkType Connected Constraints
                 */
                mRequest = OneTimeWorkRequest.Builder(NotificationWorker::class.java)
                    .setConstraints(mConstraints).build()
            }
            else -> {
            }
        }
        /**
         * Fetch the particular task status using request ID
         */
        mWorkManager!!.getWorkInfoByIdLiveData(mRequest!!.id).observe(this, { workInfo ->
            if (workInfo != null) {
                val state = workInfo.state
                tvStatus!!.append(
                    """
    $state
    
    """.trimIndent()
                )
            }
        })
        /**
         * Enqueue the WorkRequest
         */
        mWorkManager!!.enqueue(mRequest!!)
    }

    companion object {
        const val MESSAGE_STATUS = "message_status"
    }
}

Demo Application Output

In this section, we will see the demo application output screen as below. We click on Send Notification button. The Job status will show on TextView.

WorkManager Demo Application Output 1
WorkManager Demo Application Output 2

That’s all about in this article.

Related Other Articles / Posts

Conclusion

In this article, we understood about how to schedule tasks with WorkManager. This article explained about WorkManager workflow and advantages in Android application.

Thanks for reading! I hope you enjoyed and learned about WorkManager concepts in Android. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Android – How To Run Android Tasks In Background Threads ?

Hello Readers, CoolMonkTechie heartily welcomes you in this article (How To Run Android Tasks In Background Threads ?).

In this article, we will learn about how to run android tasks in background threads. All Android apps use a main thread to handle UI operations. Calling long-running operations from this main thread can lead to freezes and unresponsiveness. For example, if our app makes a network request from the main thread, our app’s UI is frozen until it receives the network response. We can create additional background threads to handle long-running operations while the main thread continues to handle UI updates.

This article shows both Kotlin and Java Programming Language developers to use of a thread pool to set up and use multiple threads in an Android app. It also explains code definitions to run on a thread and communications between one of these threads and the main thread.

A famous quote about learning is :

That is what learning is. You suddenly understand something you’ve understood all your life, but in a new way.

So Let’s begin.

Overview

In this example section, we will make a network request and return the result to the main thread, where the app then might display that result on the screen. Specifically, the ViewModel calls the repository layer on the main thread to trigger the network request. The repository layer is in charge of moving the execution of the network request off the main thread and posting the result back to the main thread using a callback.

To move the execution of the network request off the main thread, we need to create other threads in our app.

Creating Multiple Threads

thread pool is a managed collection of threads that runs tasks in parallel from a queue. New tasks are executed on existing threads as those threads become idle. To send a task to a thread pool, use the ExecutorService interface. Note that ExecutorService has nothing to do with Services, the Android application component.

Creating threads is expensive, so we should create a thread pool only once as our app initializes. Be sure to save the instance of the ExecutorService either in our Application class or in a dependency injection container. The following example creates a thread pool of four threads that we can use to run background tasks.

class MyApplication : Application() {
    val executorService: ExecutorService = Executors.newFixedThreadPool(4)
}

Executing In A Background Thread

Making a network request on the main thread causes the thread to wait, or block, until it receives a response. Since the thread is blocked, the OS can’t call onDraw(), and our app freezes, potentially leading to an Application Not Responding (ANR) dialog. Instead, let’s run this operation on a background thread.

First, let’s take a look at our Repository class and see how it’s making the network request:

sealed class Result<out R> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}

class LoginRepository(private val responseParser: LoginResponseParser) {
    private const val loginUrl = "https://example.com/login"

    // Function that makes the network request, blocking the current thread
    fun makeLoginRequest(
        jsonBody: String
    ): Result<LoginResponse> {
        val url = URL(loginUrl)
        (url.openConnection() as? HttpURLConnection)?.run {
            requestMethod = "POST"
            setRequestProperty("Content-Type", "application/json; charset=utf-8")
            setRequestProperty("Accept", "application/json")
            doOutput = true
            outputStream.write(jsonBody.toByteArray())

            return Result.Success(responseParser.parse(inputStream))
        }
        return Result.Error(Exception("Cannot open HttpURLConnection"))
    }
}

makeLoginRequest() is synchronous and blocks the calling thread. To model the response of the network request, we have our own Result class.

The ViewModel triggers the network request when the user taps, for example, on a button:

class LoginViewModel(
    private val loginRepository: LoginRepository
) {
    fun makeLoginRequest(username: String, token: String) {
        val jsonBody = "{ username: \"$username\", token: \"$token\"}"
        loginRepository.makeLoginRequest(jsonBody)
    }
}

With the previous code, LoginViewModel is blocking the main thread when making the network request. We can use the thread pool that we’ve instantiated to move the execution to a background thread. First, following the principles of dependency injectionLoginRepository takes an instance of Executor as opposed to ExecutorService because it’s executing code and not managing threads:

class LoginRepository(
    private val responseParser: LoginResponseParser
    private val executor: Executor
) { ... }

The Executor’s execute() method takes a Runnable. A Runnable is a Single Abstract Method (SAM) interface with a run() method that is executed in a thread when invoked.

Let’s create another function called makeLoginRequest() that moves the execution to the background thread and ignores the response for now:

class LoginRepository(
    private val responseParser: LoginResponseParser
    private val executor: Executor
) {

    fun makeLoginRequest(jsonBody: String) {
        executor.execute {
            val ignoredResponse = makeSynchronousLoginRequest(url, jsonBody)
        }
    }

    private fun makeSynchronousLoginRequest(
        jsonBody: String
    ): Result<LoginResponse> {
        ... // HttpURLConnection logic
    }
}

Inside the execute() method, we create a new Runnable with the block of code, we want to execute in the background thread—in our case, the synchronous network request method. Internally, the ExecutorService manages the Runnable and executes it in an available thread.

In Kotlin, we can use a lambda expression to create an anonymous class that implements the SAM interface.

Considerations

Any thread in our app can run in parallel to other threads, including the main thread, so we should ensure that our code is thread-safe. Notice that in our example that we avoid writing to variables shared between threads, passing immutable data instead. This is a good practice, because each thread works with its own instance of data, and we avoid the complexity of synchronization.

If we need to share state between threads, we must be careful to manage access from threads using synchronization mechanisms such as locks. In general we should avoid sharing mutable state between threads whenever possible.

Communicating With The Main Thread

In the previous step, we ignored the network request response. To display the result on the screen, LoginViewModel needs to know about it. We can do that by using callbacks.

The function makeLoginRequest() should take a callback as a parameter so that it can return a value asynchronously. The callback with the result is called whenever the network request completes or a failure occurs. In Kotlin, we can use a higher-order function.

class LoginRepository(
    private val responseParser: LoginResponseParser
    private val executor: Executor
) {

    fun makeLoginRequest(
        jsonBody: String,
        callback: (Result<LoginResponse>) -> Unit
    ) {
        executor.execute {
            try {
                val response = makeSynchronousLoginRequest(jsonBody)
                callback(response)
            } catch (e: Exception) {
                val errorResult = Result.Error(e)
                callback(errorResult)
            }
        }
    }
    ...
}

The ViewModel needs to implement the callback now. It can perform different logic depending on the result:

class LoginViewModel(
    private val loginRepository: LoginRepository
) {
    fun makeLoginRequest(username: String, token: String) {
        val jsonBody = "{ username: \"$username\", token: \"$token\"}"
        loginRepository.makeLoginRequest(jsonBody) { result ->
            when(result) {
                is Result.Success<LoginResponse> -> // Happy path
                else -> // Show error in UI
            }
        }
    }
}

In this example, the callback is executed in the calling thread, which is a background thread. This means that we cannot modify or communicate directly with the UI layer until we switch back to the main thread.

To communicate with the View from the ViewModel layer, use LiveData as recommended in the updated app architecture. If the code is being executed on a background thread, we can call MutableLiveData.postValue() to communicate with the UI layer.

Using Handlers

We can use a Handler to enqueue an action to be performed on a different thread. To specify the thread on which to run the action, construct the Handler using a Looper for the thread. A Looper is an object that runs the message loop for an associated thread. Once we’ve created a Handler, we can then use the post(Runnable) method to run a block of code in the corresponding thread.

Looper includes a helper function, getMainLooper(), which retrieves the Looper of the main thread. We can run code in the main thread by using this Looper to create a Handler. As this is something we might do quite often, we can also save an instance of the Handler in the same place we saved the ExecutorService:

class MyApplication : Application() {
    val executorService: ExecutorService = Executors.newFixedThreadPool(4)
    val mainThreadHandler: Handler = HandlerCompat.createAsync(Looper.getMainLooper())
}

It’s a good practice to inject the handler to the Repository, as it gives us more flexibility. For example, in the future we might want to pass in a different Handler to schedule tasks on a separate thread. If we’re always communicating back to the same thread, we can pass the Handler into the Repository constructor, as shown in the following example.

class LoginRepository(
    ...
    private val resultHandler: Handler
) {

    fun makeLoginRequest(
        jsonBody: String,
        callback: (Result<LoginResponse>) -> Unit
    ) {
          executor.execute {
              try {
                  val response = makeSynchronousLoginRequest(jsonBody)
                  resultHandler.post { callback(response) }
              } catch (e: Exception) {
                  val errorResult = Result.Error(e)
                  resultHandler.post { callback(errorResult) }
              }
          }
    }
    ...
}

Alternatively, if we want more flexibility, we can pass in a Handler to each function:

class LoginRepository(...) {
    ...
    fun makeLoginRequest(
        jsonBody: String,
        resultHandler: Handler,
        callback: (Result<LoginResponse>) -> Unit
    ) {
        executor.execute {
            try {
                val response = makeSynchronousLoginRequest(jsonBody)
                resultHandler.post { callback(response) }
            } catch (e: Exception) {
                val errorResult = Result.Error(e)
                resultHandler.post { callback(errorResult) }
            }
        }
    }
}

In this example, the callback passed into the Repository’s makeLoginRequest call is executed on the main thread. That means we can directly modify the UI from the callback or use LiveData.setValue() to communicate with the UI.

Configuring A Thread Pool

We can create a thread pool using one of the Executor helper functions with predefined settings, as shown in the previous example code. Alternatively, if we want to customize the details of the thread pool, we can create an instance using ThreadPoolExecutor directly. We can configure the following details:

  • Initial and maximum pool size
  • Keep alive time and time unit. Keep alive time is the maximum duration that a thread can remain idle before it shuts down.
  • An input queue that holds Runnable tasks. This queue must implement the BlockingQueue interface. To match the requirements of our app, we can choose from the available queue implementations.

Here’s an example that specifies thread pool size based on the total number of processor cores, a keep alive time of one second, and an input queue.

class MyApplication : Application() {
    /*
     * Gets the number of available cores
     * (not always the same as the maximum number of cores)
     */
    private val NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors()

    // Instantiates the queue of Runnables as a LinkedBlockingQueue
    private val workQueue: BlockingQueue<Runnable> =
            LinkedBlockingQueue<Runnable>()

    // Sets the amount of time an idle thread waits before terminating
    private const val KEEP_ALIVE_TIME = 1L
    // Sets the Time Unit to seconds
    private val KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS
    // Creates a thread pool manager
    private val threadPoolExecutor: ThreadPoolExecutor = ThreadPoolExecutor(
            NUMBER_OF_CORES,       // Initial pool size
            NUMBER_OF_CORES,       // Max pool size
            KEEP_ALIVE_TIME,
            KEEP_ALIVE_TIME_UNIT,
            workQueue
    )
}

Concurrency Libraries

It’s important to understand the basics of threading and its underlying mechanisms. There are, however, many popular libraries that offer higher-level abstractions over these concepts and ready-to-use utilities for passing data between threads. These libraries include Guava and RxJava for the Java Programming Language users and coroutines, which we recommend for Kotlin users.

In practice, we should pick the one that works best for our app and our development team, though the rules of threading remain the same.

That’s all about in this article.

Related Other Articles / Posts

Conclusion

In this article, we understood about how to run android tasks in background threads. This article showed both Kotlin and Java Programming Language developers to use of a thread pool to set up and use multiple threads in an Android app. It also explained code definitions to run on a thread and communications between one of these threads and the main thread.

Thanks for reading! I hope you enjoyed and learned about Running Android Tasks In Background Threads concepts. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Android – How To Apply Background Processing In Android Application?

Hello Readers, CoolMonkTechie heartily welcomes you in this article (How To Apply Background Processing In Android Application?).

In this article, we will learn about how to apply background processing in android application. Processing data in the background is an important part of creating an Android application. Therefore, it is both responsive for our users as well as a good citizen on the Android platform. This article explains the below points :

  • what qualifies as background work,
  • defines background task categories,
  • provides us with criteria to categorize our tasks, and
  • recommends APIs that we should use to execute them.

A famous quote about learning is :

Learn as though you would never be able to master it; hold it as though you would be in fear of losing it.

So Let’s begin.

Background Processing Principle

In general, any task that takes more than a few milliseconds should be delegated to a background thread. Common long-running tasks include things like decoding a bitmap, accessing storage, working on a machine learning (ML) model, or performing network requests.

Background Work Definition

An app is considered to be running in the background as long as each of the following conditions are satisfied:

  • None of the app’s activities are currently visible to the user.
  • The app isn’t running any foreground services that started while an activity from the app was visible to the user.

Otherwise, the app is considered to be running in the foreground.

Sometimes, after a foreground service starts while an activity from the app is visible to the user, the user navigates away from the app, such as returning to the home screen. In this situation, the app is considered to be running in the foreground even after the user navigates away from the app.

Common Background Tasks

The following list shows common tasks that an app manages while it runs in the background:

  • Our app registers a broadcast receiver in the manifest file.
  • Existing app schedules a repeating alarm using Alarm Manager.
  • Currently used app schedules a background task, either as a worker using Work Manager or a job using Job Scheduler.

Categories of Background Tasks

Background tasks fall into one of the following main categories:

  • Immediate
  • Deferred
  • Exact

To categorize a task, we need answer the following questions:

Does the task need to complete while the user is interacting with the application?

If so, this task should be categorized for immediate execution. If not, proceed to the second question.

Does the task need to run at an exact time?

If we do need to run a task at an exact time, categorize the task as exact.

Most tasks don’t need to be run at an exact time. Tasks generally allow for slight variations in when they run that are based on conditions such as network availability and remaining battery. Tasks that don’t need to be run at an exact time should be categorized as deferred.

After getting the answer, we traverse the corresponding decision tree which helps us decide best category for our background task.

Source : Android Developers – Task Category Decision Tree

Recommended Solutions

The following sections describe recommended solutions for each background task type.

Immediate Tasks

We recommend Kotlin coroutines for tasks that should end when the user leaves a certain scope or finishes an interaction. Many Android KTX libraries contain ready-to-use coroutine scopes for common app components like ViewModel and common application lifecycles.

Threading on Android is the recommended options for Java programming language users.

For tasks that should be executed immediately and need continued processing, even if the user puts the application in background or the device restarts, we recommend using WorkManager and its support for long-running tasks.

In specific cases, such as with media playback or active navigation, we might want to use foreground Services directly.

Deferred Tasks

A task can deferred if:

  • it is not directly connected to a user interaction and,
  • it can run at any time in the future.

The recommended solution for deferred tasks is WorkManager.

WorkManager makes it easy to schedule deferrable, asynchronous tasks that are expected to run even if the app exits or the device restarts.

Exact Tasks

We can use AlarmManager if task that needs to be executed at an exact point in time.

That’s all about in this article.

Related Other Articles / Posts

Conclusion

In this article, we understood about how to apply background processing in android application. Processing data in the background is an important part of creating an Android application. We can use WorkManager for Immediate and deferred task and continued processing while AlarmManager for Exact Tasks. This article reviewed background work definitions and qualifies, common categories and recommended solutions for background processing tasks in android.

Thanks for reading! I hope you enjoyed and learned about Background Processing concepts in Android. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

React Native – How To Apply Run Time Android Permission In React Native ?

Hello Readers, CoolMonkTechie heartily welcomes you in this article ( How To Apply Run Time Android Permission In React Native ).

In this article, we will learn how to apply Run Time Android Permission In React Native. Android Permission feature helps a user to know which application is trying to access sensitive data such as files, contacts and SMS, and system-specific features such as camera and internet. This article shows to apply the run time permission with the authentic example.

To understand the Run Time Android Permission In React Native, we cover the below topics as below :

  • What is Android Permission?
  • Why we need permission?
  • How to apply Permission in Android?
  • How to apply run time Permission in React Native?
  • Example to apply the run time permission.

A famous quote about learning is :

” The great aim of education is not knowledge, but action. “

So Let’s begin.


What is Android Permission ?

According to Google’s privacy policy, a user should know which application is trying to access sensitive data such as files, contacts and SMS, and system-specific features such as camera and internet. So they introduced the Android Permission feature.

In Android permission modal, every application which is using sensitive data or some certain system features has to ask the permissions from the user.

The purpose of permission is to protect the privacy of an Android user.


Why We need Permission ?

Android applies the permission modal because some applications were tracking the user’s location in the background and accessing the private data like contacts, call history and messages without informing the user which is the violation of googles privacy policy.

So solving this sensitive issue application has to ask for permission to access those sensitive data.


How to Apply Permission in Android ?

Now we get an idea about the Android permission, let’s see how to apply the permission in Android.

To apply permission in Android, we have to follow two steps:

  1. We have to add those permissions requests in project -> app -> src -> AndroidManifest.xml file.

For Example, if we want to ask for the device location Permission, we have to add the following permission request in AndroidManifest.xml :

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

After adding the permission in AndroidManifest file, this permission will be automatically asked by the play store when they install the app and this permission will be granted to the applications.

On devices before SDK version 23, the permissions are automatically granted if we add those permissions in our AndroidManifest.xml file, but after SDK version 23 we have to ask runtime permission as well.

2. After Google’s new permission modal, they have introduced the run time permission mechanism in Android version 23. According to that, we have to include all the permissions in AndroidManifest.xml and also have to apply some dangerous permissions in run time.

According to the official docs, dangerous permissions require a dialog prompt.

So whenever we are working with this dangerous permissions, we need to check whether the permission is granted by the user, and that can be done with the help of PermissionsAndroid in React Native.

For example, If we need INTERNET permission and CAMERA permission then we have to add both the permission in AndroidManifest.xml file but have to ask only for the CAMERA permission in run time because CAMERA permission comes under the dangerous permission not INTERNET permission.

Run Time Permissions that requires Confirmation from the User. Here is the list of dangerous permissions.

READ_CALENDARandroid.permission.READ_CALENDAR
WRITE_CALENDARandroid.permission.WRITE_CALENDAR
CAMERAandroid.permission.CAMERA
READ_CONTACTSandroid.permission.READ_CONTACTS
WRITE_CONTACTSandroid.permission.WRITE_CONTACTS
GET_ACCOUNTSandroid.permission.GET_ACCOUNTS
ACCESS_FINE_LOCATIONandroid.permission.ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATIONandroid.permission.ACCESS_COARSE_LOCATION
RECORD_AUDIOandroid.permission.RECORD_AUDIO
READ_PHONE_STATEandroid.permission.READ_PHONE_STATE
CALL_PHONEandroid.permission.CALL_PHONE
READ_CALL_LOGandroid.permission.READ_CALL_LOG
WRITE_CALL_LOGandroid.permission.WRITE_CALL_LOG
ADD_VOICEMAILcom.android.voicemail
USE_SIPandroid.permission.USE_SIP
PROCESS_OUTGOING_CALLSandroid.permission.PROCESS_OUTGOING_CALLS
BODY_SENSORSandroid.permission.BODY_SENSORS
SEND_SMSandroid.permission.SEND_SMS
RECEIVE_SMSandroid.permission.RECEIVE_SMS
READ_SMSandroid.permission.READ_SMS
RECEIVE_WAP_PUSHandroid.permission.RECEIVE_WAP_PUSH
RECEIVE_MMSandroid.permission.RECEIVE_MMS
READ_EXTERNAL_STORAGEandroid.permission.READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGEandroid.permission.WRITE_EXTERNAL_STORAGE
Run Time Permissions


How to apply Run Time Android Permission using React Native PermissionsAndroid ?

In React Native, PermissionsAndroid component provides access to Android M’s (Over API level 23) new permissions model. We always need to check the permission before using native APIs which comes under dangerous permissions.

Above mentioned all the dangerous permissions comes under PermissionsAndroid.PERMISSIONS as constants.

We can check the permission granted or not using PermissionsAndroid.RESULTS.GRANTED.

To apply the permission to use

PermissionsAndroid.request(permission name,{Permission dialog heading, body})

Result Strings for Requesting Permissions

As constants under PermissionsAndroid.RESULTS:

ResponseResult
GRANTEDPermission granted successfully by the user
DENIEDPermission denied by the user
NEVER_ASK_AGAINPermission denied by the user with never ask again.
Result Strings for Requesting Permissions

Now we get an idea about Android permissions and components. Let’s move towards the Example which can help us apply permission in our application.


Example to Apply the Run Time Permission

In this example, we will apply the device location permission, which needs run time permission. We are making a button on the centre of the screen and on a click of button we will apply the run time permission for device location known as ACCESS_FINE_LOCATION. So let’s get started.


Example Project Setup

To demonstration of apply the Run Time Permission, we have to follow the below steps:

  • Create a new React Native project
  • Adding device location permission in AndroidManifest.xml


1. Create a new React Native project

Assuming that we have node installed, we can use npm to install the react-native-cli command line utility. Open the terminal and go to the workspace and run the following commands to create a new React Native project.

npx react-native init ProjectName

This will make a project structure with an index file named App.js in our project directory.


2. Adding device location permission in AndroidManifest.xml

If we are using any features in our app that needs permission, we need to add it in AndroidManifest.xml. For this example, we are adding device location permission in AndroidManifest.xml.

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
Android Permission

Now jump into the project using

cd ProjectName


Example Code to Apply for the Run Time Permission

Open App.js in any code editor and replace the code with the following code.

App.js

import React, {Component} from 'react';

import {
  Platform,
  StyleSheet,
  View,
  PermissionsAndroid,
  Text,
  Alert,
} from 'react-native';

export async function request_location_runtime_permission() {
  try {
    const granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
      {
        title: 'ReactNativeCode Location Permission',
        message: 'ReactNativeCode App needs access to your location ',
      },
    );
    if (granted === PermissionsAndroid.RESULTS.GRANTED) {
      Alert.alert('Location Permission Granted.');
    } else {
      Alert.alert('Location Permission Not Granted');
    }
  } catch (err) {
    console.warn(err);
  }
}

export default class App extends Component {
  async componentDidMount() {
    await request_location_runtime_permission();
  }

  render() {
    return (
      <View style={styles.MainContainer}>
        <Text>React Native Runtime Permission Request</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  MainContainer: {
    flex: 1,
    paddingTop: Platform.OS === 'ios' ? 20 : 0,
    justifyContent: 'center',
    margin: 20,
  },
});

Here PermissionsAndroid.request has two arguments:

  1. PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION to generate a dialog to ask for Device Location Permission.
  2. A JSON {‘title’:”,’message’:”}, which will help us to communicate or to show the dialog about the permission if the user denied the permission. They will generate it like this.
Android Permission Dialog Box


To Run the React Native Example Code

Open the terminal again, and jump into our project using

cd ProjectName

To run the project on an Android Virtual Device or on real debugging device

npx react-native run-android

or on the iOS Simulator by running (macOS only)

npx react-native run-ios (macOS only).

The output of example code is as below:

Android Permission Grant Example Output Screenshot

That’s all about in this article.


Conclusion

In this article, we understood how to apply Run Time Android Permission in React Native. This article showed the example code to apply the RunTime Android Permission in React Native.

Thanks for reading! I hope you enjoyed and learned about RunTime Android Permission Concepts in React Native. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe us on this blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find Other articles of CoolmonkTechie as below link :

You can also follow official website and tutorials of React Native as below links :

React Native Official Website

If you have any comments, questions, or think I missed something, feel free to leave them below in the comment box.

Thanks again Reading. HAPPY READING!!???

Android – How To Select Scope Functions In Kotlin?

Hello Readers, CoolMonkTechie heartily welcomes you in this article (How To Select Scope Functions In Kotlin?).

In this article, we will learn about how to select Scope Functions in Kotlin. The Kotlin standard library contains several functions whose sole purpose is to execute a block of code within the context of an object. When we call such a function on an object with a lambda expression provided, it forms a temporary scope. In this scope, we can access the object without its name. Such functions are called Scope Functions. This article provides the detailed descriptions of the differences between scope functions and the conventions on their usage in Kotlin with some authentic examples.

A famous quote about learning is :

” Tell me and I forget, teach me and I may remember, involve me and I learn.”

So Let’s begin.

Scope Functions in Kotlin

The definition of Scope function is

Scoped functions are functions that execute a block of code within the context of an object.

These functions provide a way to give temporary scope to the object under consideration where specific operations can be applied to the object within the block of code, thereby, resulting in a clean and concise code. There are five scoped functions in Kotlin: letrunwithalso and apply.

Basically, these functions do the same: execute a block of code on an object. What’s different is how this object becomes available inside the block and what is the result of the whole expression.

Example

Here’s a typical usage of a scope function:

Person("Alice", 20, "Amsterdam").let {
     println(it)
     it.moveTo("London")
     it.incrementAge()
     println(it)
 }

If we write the same without let, we’ll have to introduce a new variable and repeat its name whenever we use it.

val alice = Person("Alice", 20, "Amsterdam")
 println(alice)
 alice.moveTo("London")
 alice.incrementAge()
 println(alice)

The scope functions do not introduce any new technical capabilities, but they can make our code more concise and readable.

Due to the similar nature of scope functions, choosing the right one for our case can be a bit tricky. The choice mainly depends on our intent and the consistency of use in our project. 

Common Difference between Scope Functions

Because the scope functions are all quite similar in nature, it’s important to understand the differences between them. There are two main differences between each scope function:

  • The way to refer to the context object
  • The return value.

Context object – this or it

Inside the lambda of a scope function, the context object is available by a short reference instead of its actual name. Each scope function uses one of two ways to access the context object: as a lambda receiver (this) or as a lambda argument (it). Both provide the same capabilities, so we’ll describe the pros and cons of each for different cases and provide recommendations on their use.

fun main() {
     val str = "Hello"
     // this
     str.run {
         println("The receiver string length: $length")
         //println("The receiver string length: ${this.length}") // does the same
     }
     // it 
     str.let {     
        println("The receiver string's length is ${it.length}")           }
}

this

runwith, and apply refer to the context object as a lambda receiver – by keyword this. Hence, in their lambdas, the object is available as it would be in ordinary class functions. In most cases, we can omit this when accessing the members of the receiver object, making the code shorter. On the other hand, if this is omitted, it can be hard to distinguish between the receiver members and external objects or functions. So, having the context object as a receiver (this) is recommended for lambdas that mainly operate on the object members: call its functions or assign properties.

val adam = Person("Adam").apply { 
     age = 20                       // same as this.age = 20 or adam.age = 20
     city = "London"
 }
 println(adam)

it

In turn, let and also have the context object as a lambda argument. If the argument name is not specified, the object is accessed by the implicit default name itit is shorter than this and expressions with it are usually easier for reading. However, when calling the object functions or properties we don’t have the object available implicitly like this. Hence, having the context object as it is better when the object is mostly used as an argument in function calls. it is also better if we use multiple variables in the code block.

fun getRandomInt(): Int {
     return Random.nextInt(100).also {
         writeToLog("getRandomInt() generated value $it")
     }
 }
 val i = getRandomInt()

Additionally, when we pass the context object as an argument, we can provide a custom name for the context object inside the scope.

fun getRandomInt(): Int {
     return Random.nextInt(100).also { value ->
         writeToLog("getRandomInt() generated value $value")
     }
 }
 val i = getRandomInt()

Return value

The scope functions differ by the result they return:

  • apply and also return the context object.
  • letrun, and with return the lambda result.

These two options let we choose the proper function depending on what we do next in our code.

Context object

The return value of apply and also is the context object itself. Hence, they can be included into call chains as side steps: we can continue chaining function calls on the same object after them.

val numberList = mutableListOf()
 numberList.also { println("Populating the list") }
     .apply {
         add(2.71)
         add(3.14)
         add(1.0)
     }
     .also { println("Sorting the list") }
     .sort()

They also can be used in return statements of functions returning the context object.

fun getRandomInt(): Int {
     return Random.nextInt(100).also {
         writeToLog("getRandomInt() generated value $it")
     }
 }
 val i = getRandomInt()

Lambda result

letrun, and with return the lambda result. So, we can use them when assigning the result to a variable, chaining operations on the result, and so on.

val numbers = mutableListOf("one", "two", "three")
 val countEndsWithE = numbers.run { 
     add("four")
     add("five")
     count { it.endsWith("e") }
 }
 println("There are $countEndsWithE elements that end with e.")

Additionally, we can ignore the return value and use a scope function to create a temporary scope for variables.

val numbers = mutableListOf("one", "two", "three")
 with(numbers) {
     val firstItem = first()
     val lastItem = last()        
     println("First item: $firstItem, last item: $lastItem")
 }

We can analyze the common scope functions difference summary as below diagram:

Scoped Functions Summary

Five Scope Functions In Kotlin

To help we choose the right scope function for our case, we’ll describe them in detail and provide usage recommendations. Technically, functions are interchangeable in many cases, so the examples show the conventions that define the common usage style.

There are five scoped functions in Kotlin: letrunwithalso and apply. 

Let’s go through them one by one.

Scope Function – let

The context object is available as an argument (it). The return value is the lambda result.

let can be used to invoke one or more functions on results of call chains. For example, the following code prints the results of two operations on a collection:

val numbers = mutableListOf("one", "two", "three", "four", "five")
 val resultList = numbers.map { it.length }.filter { it > 3 }
 println(resultList)    

With let, we can rewrite it:

val numbers = mutableListOf("one", "two", "three", "four", "five")
 numbers.map { it.length }.filter { it > 3 }.let { 
     println(it)
     // and more function calls if needed
 } 

If the code block contains a single function with it as an argument, we can use the method reference (::) instead of the lambda:

val numbers = mutableListOf("one", "two", "three", "four", "five")
 numbers.map { it.length }.filter { it > 3 }.let(::println)

let is often used for executing a code block only with non-null values. To perform actions on a non-null object, use the safe call operator ?. on it and call let with the actions in its lambda.

val str: String? = "Hello"   
 //processNonNullString(str)       // compilation error: str can be null
 val length = str?.let { 
     println("let() called on $it")        
     processNonNullString(it)      // OK: 'it' is not null inside '?.let { }'
     it.length
 }

Another case for using let is introducing local variables with a limited scope for improving code readability. To define a new variable for the context object, provide its name as the lambda argument so that it can be used instead of the default it.

val numbers = listOf("one", "two", "three", "four")
 val modifiedFirstItem = numbers.first().let { firstItem ->
     println("The first item of the list is '$firstItem'")
     if (firstItem.length >= 5) firstItem else "!" + firstItem + "!"
 }.toUpperCase()
 println("First item after modifications: '$modifiedFirstItem'")

Scope Function – with

A non-extension function: the context object is passed as an argument, but inside the lambda, it’s available as a receiver (this). The return value is the lambda result.

We recommend with for calling functions on the context object without providing the lambda result. In the code, with can be read as “with this object, do the following.

val numbers = mutableListOf("one", "two", "three")
 with(numbers) {
     println("'with' is called with argument $this")
     println("It contains $size elements")
 }

Another use case for with is introducing a helper object whose properties or functions will be used for calculating a value.

val numbers = mutableListOf("one", "two", "three")
 val firstAndLast = with(numbers) {
     "The first element is ${first()}," +
     " the last element is ${last()}"
 }
 println(firstAndLast)

It is convenient when we have to call multiple different methods on the same object. Instead of repeating the variable containing this object on each line, we can use withThis function is used to change instance properties without the need to call dot operator over the reference every time.

Scope Function – run

The context object is available as a receiver (this). The return value is the lambda result.

run does the same as with but invokes as let – as an extension function of the context object.

run is useful when our lambda contains both the object initialization and the computation of the return value.

val service = MultiportService("https://example.kotlinlang.org", 80)
 val result = service.run {
     port = 8080
     query(prepareRequest() + " to port $port")
 }
 // the same code written with let() function:
 val letResult = service.let {
     it.port = 8080
     it.query(it.prepareRequest() + " to port ${it.port}")
 }

Besides calling run on a receiver object, we can use it as a non-extension function. Non-extension run lets us execute a block of several statements where an expression is required.

val hexNumberRegex = run {
     val digits = "0-9"
     val hexDigits = "A-Fa-f"
     val sign = "+-"
 Regex("[$sign]?[$digits$hexDigits]+")
 }
 for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) {
     println(match.value)
 }

run is actually a combination of with() and let().

Scope Function – apply

The context object is available as a receiver (this). The return value is the object itself.

Use apply for code blocks that don’t return a value and mainly operate on the members of the receiver object. The common case for apply is the object configuration. Such calls can be read as “apply the following assignments to the object.

val adam = Person("Adam").apply {
     age = 32
     city = "London"        
 }
 println(adam)

Having the receiver as the return value, we can easily include apply into call chains for more complex processing.

Scope Function – also

The context object is available as an argument (it). The return value is the object itself.

also is good for performing some actions that take the context object as an argument. Use also for actions that need a reference rather to the object than to its properties and functions, or when we don’t want to shadow this reference from an outer scope.

When we see also in the code, we can read it as “and also do the following with the object.

val numbers = mutableListOf("one", "two", "three")
 numbers
     .also { println("The list elements before adding new one: $it") }
     .add("four")

Scope Functions – run vs let

run is similar to let in terms of accepting any return value , but this is different in the context of the object terms. run function refers to the context of the object as “this” and not “it”. That is the reason we did not use “${this.name}” as it would be redundant here since the block of code understands that “name” is used here concerning the Person object.

Scope Functions – run redundant this

Another point here is that since the context is referred to as “this”, it cannot be renamed to a readable lambda parameter. So depending on the use case and requirement , we have to choose between the let and the run operator. The “run” operator also helps in easy null checks similar to the “let” operator.

var name: String? = "Abcd" 
private fun performRunOperation() {     
val name = Person().name?.run {         
"The name of the Person is: $this"     
}     
print(name) 
} 

Scope Functions – with vs run

Let’s consider a case where a Person object can be nullable.

Scope Functions With nullable Value

we can see that the context of the object referred to as “this” is a nullable type of Person. And hence, to correct this, we need to change the code as:

private fun performWithOperation() {     
val person: Person? = null     
with(person) {         
  this?.name = "asdf"         
  this?.contactNumber = "1234"         
  this?.address = "wasd"         
  this?.displayInfo()     
 } 
}

So performing a null check using a “with” operator is difficult and this is where we can replace it with “run” as follows:

private fun performRunOperation() {     
 val person: Person? = null     
 person?.run {         
   name = "asdf"         
   contactNumber = "1234"         
   address = "wasd"         
   displayInfo()     
 } 
}

This looks a lot cleaner.

Scope Functions – run vs apply

So let’s see the difference between run and apply functions.

Scope Functions – apply-vs-run

We can see that run accepts a return statement whereas apply does not accept a return statement(we can see the error thrown by the IDE in the image) and always returns the same object which it is referring to.

Scope Functions – let vs also

So let’s see the difference between let and also functions.

Scope Functions – let-vs-also

We can see that let accepts a return statement whereas “also” does not accept a return statement(we can see the error thrown by the IDE in the image) and always returns the same object which it is referring to.

Standard Library Scope Functions – takeIf and takeUnless

In addition to scope functions, the standard library contains the functions takeIf and takeUnless. These functions let us embed checks of the object state in call chains.

When we called on an object with a predicate provided, takeIf returns this object if it matches the predicate. Otherwise, it returns null. So, takeIf is a filtering function for a single object. In turn, takeUnless returns the object if it doesn’t match the predicate and null if it does. The object is available as a lambda argument (it).

val number = Random.nextInt(100)
val evenOrNull = number.takeIf { it % 2 == 0 }
val oddOrNull = number.takeUnless { it % 2 == 0 }
println("even: $evenOrNull, odd: $oddOrNull")

When we do chaining other functions after takeIf and takeUnless, we don’t forget to perform the null check or the safe call (?.) because their return value is nullable.

val str = "Hello"
 val caps = str.takeIf { it.isNotEmpty() }?.toUpperCase()
 //val caps = str.takeIf { it.isNotEmpty() }.toUpperCase() //compilation error
 println(caps)

takeIf and takeUnless are especially useful together with scope functions. A good case is chaining them with let for running a code block on objects that match the given predicate. To do this, call takeIf on the object and then call let with a safe call (?). For objects that don’t match the predicate, takeIf returns null and let isn’t invoked.

fun displaySubstringPosition(input: String, sub: String) {
     input.indexOf(sub).takeIf { it >= 0 }?.let {
         println("The substring $sub is found in $input.")
         println("Its start position is $it.")
     }
 }
 displaySubstringPosition("010000011", "11")
 displaySubstringPosition("010000011", "12")

This is how the same function looks without the standard library functions:

fun displaySubstringPosition(input: String, sub: String) {
     val index = input.indexOf(sub)
     if (index >= 0) {
         println("The substring $sub is found in $input.")
         println("Its start position is $index.")
     }
 }
 displaySubstringPosition("010000011", "11")
 displaySubstringPosition("010000011", "12")

The Selection Of Scope Functions

To help we choose the right scope function for our purpose, we provide the table of key differences between them.

FunctionObject referenceReturn valueIs extension function
letitLambda resultYes
runthisLambda resultYes
runLambda resultNo: called without the context object
withthisLambda resultNo: takes the context object as an argument.
applythisContext objectYes
alsoitContext objectYes
Key differences between Scope Function

Here is a short guidelines for choosing scope functions depending on the intended purpose:

  • Executing a lambda on non-null objects: let
  • Introducing an expression as a variable in local scope: let
  • Object configuration: apply
  • Object configuration and computing the result: run
  • Running statements where an expression is required: non-extension run
  • Additional effects: also
  • Grouping function calls on an object: with

The use cases of different scope functions overlap, so that we can choose the functions based on the specific conventions used in our project or team.

That’s all about in this article.

Related Other Articles / Posts

Conclusion

In this article, we understood about how to select Scope Functions in Kotlin. Although the scope functions are a way of making the code more concise, avoid overusing them: it can decrease our code readability and lead to errors. Avoid nesting scope functions and be careful when chaining them: it’s easy to get confused about the current context object and the value of this or it. This article showed the detailed descriptions of the differences between scope functions and the conventions on their usage in Kotlin with some authentic examples.

Thanks for reading! I hope you enjoyed and learned about Scope Functions concepts in Kotlin. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Android – How To Apply Manual Dependency Injection In A Real Android Application ?

Hello Readers, CoolMonkTechie heartily welcomes you in this article (How To Apply Manual Dependency Injection In A Real Android Application ?).

In this article, we will learn about how to apply manual dependency injection in a real android application. Dependency Injection is a good technique for creating scalable and testable Android applications. When our application gets larger, we will start seeing that we write a lot of boilerplate code (such as factories), which can be error-prone. We also have to manage the scope and lifecycle of the containers ourself, optimizing and discarding containers that are no longer needed in order to free up memory. Doing this incorrectly can lead to subtle bugs and memory leaks in our app. This article reviews an iterated approach of how we might start using manual dependency injection in our application.

A famous quote about learning is :

” Anyone who stops learning is old, whether at twenty or eighty. Anyone who keeps learning stays young. The greatest thing in life is to keep your mind young. “

So Let’s begin.

Manual Dependency Injection

Android’s recommended app architecture encourages dividing our code into classes to benefit from separation of concerns, a principle where each class of the hierarchy has a single defined responsibility. This leads to more, smaller classes that need to be connected together to fulfill each other’s dependencies.

Source : Android Developers – A Model Of An Android App’s Application Graph

The dependencies between classes can be represented as a graph, in which each class is connected to the classes it depends on. The representation of all our classes and their dependencies makes up the application graph. In figure 1, we can see an abstraction of the application graph. When class A (ViewModel) depends on class B (Repository), there’s a line that points from A to B representing that dependency.

Dependency injection helps make these connections and enables us to swap out implementations for testing. For example, when testing a ViewModel that depends on a repository, we can pass different implementations of Repository with either fakes or mocks to test the different cases.

The approach improves until it reaches a point that is very similar to what Dagger would automatically generate for us.

Example – Login Flow For A Typical Android Application

Consider a flow to be a group of screens in our app that correspond to a feature. Login, registration, and checkout are all examples of flows.

When covering a login flow for a typical Android application, the LoginActivity depends on LoginViewModel, which in turn depends on UserRepository. Then UserRepository depends on a UserLocalDataSource and a UserRemoteDataSource, which in turn depends on a Retrofit service.

Source : Android Developers –  A Login Flow For A Typical Android Application

LoginActivity is the entry point to the login flow and the user interacts with the activity. Thus, LoginActivity needs to create the LoginViewModel with all its dependencies.

The Repository and DataSource classes of the flow look like this:

class UserRepository(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

class UserLocalDataSource { ... }
class UserRemoteDataSource(
    private val loginService: LoginRetrofitService
) { ... }

Here’s what LoginActivity looks like:

class LoginActivity: Activity() {

    private lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // In order to satisfy the dependencies of LoginViewModel, you have to also
        // satisfy the dependencies of all of its dependencies recursively.
        // First, create retrofit which is the dependency of UserRemoteDataSource
        val retrofit = Retrofit.Builder()
            .baseUrl("https://example.com")
            .build()
            .create(LoginService::class.java)

        // Then, satisfy the dependencies of UserRepository
        val remoteDataSource = UserRemoteDataSource(retrofit)
        val localDataSource = UserLocalDataSource()

        // Now you can create an instance of UserRepository //that LoginViewModel needs
        val userRepository = UserRepository(localDataSource, remoteDataSource)

        // Lastly, create an instance of LoginViewModel with //userRepository
        loginViewModel = LoginViewModel(userRepository)
    }
}

There are issues with this approach:

  • There’s a lot of boilerplate code. If we wanted to create another instance of LoginViewModel in another part of the code, we’d have code duplication.
  • Dependencies have to be declared in order. We have to instantiate UserRepository before LoginViewModel in order to create it.
  • It’s difficult to reuse objects. If we wanted to reuse UserRepository across multiple features, we’d have to make it follow the singleton pattern. The singleton pattern makes testing more difficult because all tests share the same singleton instance.

Example Solutions

Managing Dependencies With A Container

To solve the issue of reusing objects, we can create our own dependencies container class that we use to get dependencies. All instances provided by this container can be public. In the example, because we only need an instance of UserRepository, we can make its dependencies private with the option of making them public in the future if they need to be provided:

// Container of objects shared across the whole app
class AppContainer {

    // Since you want to expose userRepository out of the container, you need to satisfy
    // its dependencies as you did before
    private val retrofit = Retrofit.Builder()
                            .baseUrl("https://example.com")
                            .build()
                            .create(LoginService::class.java)

    private val remoteDataSource = UserRemoteDataSource(retrofit)
    private val localDataSource = UserLocalDataSource()

    // userRepository is not private; it'll be exposed
    val userRepository = UserRepository(localDataSource, remoteDataSource)
}

Because these dependencies are used across the whole application, they need to be placed in a common place all activities can use: the application class. Create a custom application class that contains an AppContainer instance.

// Custom Application class that needs to be specified
// in the AndroidManifest.xml file
class MyApplication : Application() {

    // Instance of AppContainer that will be used by all the Activities of the app
    val appContainer = AppContainer()
}

We aware that AppContainer is just a regular class with a unique instance shared across the app placed in our application class. However, AppContainer is not following the singleton pattern; in Kotlin, it’s not an object, and in Java, it’s not accessed with the typical Singleton.getInstance() method.

Now we can get the instance of the AppContainer from the application and obtain the shared of UserRepository instance:

class LoginActivity: Activity() {

    private lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Gets userRepository from the instance of AppContainer in Application
        val appContainer = (application as MyApplication).appContainer
        loginViewModel = LoginViewModel(appContainer.userRepository)
    }
}

In this way, we don’t have a singleton UserRepository. Instead, we have an AppContainer shared across all activities that contains objects from the graph and creates instances of those objects that other classes can consume.

If LoginViewModel is needed in more places in the application, having a centralized place where we create instances of LoginViewModel makes sense. We can move the creation of LoginViewModel to the container and provide new objects of that type with a factory. The code for a LoginViewModelFactory looks like this:

// Definition of a Factory interface with a function to create objects of a type
interface Factory<T> {
    fun create(): T
}

// Factory for LoginViewModel.
// Since LoginViewModel depends on UserRepository, in order to create instances of
// LoginViewModel, you need an instance of UserRepository that you pass as a parameter.
class LoginViewModelFactory(private val userRepository: UserRepository) : Factory {
    override fun create(): LoginViewModel {
        return LoginViewModel(userRepository)
    }
}

We can include the LoginViewModelFactory in the AppContainer and make the LoginActivity consume it:

// AppContainer can now provide instances of LoginViewModel with LoginViewModelFactory
class AppContainer {
    ...
    val userRepository = UserRepository(localDataSource, remoteDataSource)

    val loginViewModelFactory = LoginViewModelFactory(userRepository)
}

class LoginActivity: Activity() {

    private lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Gets LoginViewModelFactory from the application instance of AppContainer
        // to create a new LoginViewModel instance
        val appContainer = (application as MyApplication).appContainer
        loginViewModel = appContainer.loginViewModelFactory.create()
    }
}

This approach is better than the previous one, but there are still some challenges to consider:

  • We have to manage AppContainer ourself, creating instances for all dependencies by hand.
  • There is still a lot of boilerplate code. We need to create factories or parameters by hand depending on whether we want to reuse an object or not.

Managing Dependencies In Application Flows

AppContainer gets complicated when we want to include more functionality in the project. When our app becomes larger and we start introducing different feature flows, there are even more problems that arise:

  • When we have different flows, we might want objects to just live in the scope of that flow. For example, when creating LoginUserData (that might consist of the username and password used only in the login flow) we don’t want to persist data from an old login flow from a different user. We want a new instance for every new flow. We can achieve that by creating FlowContainer objects inside the AppContainer as demonstrated in the next code example.
  • Optimizing the application graph and flow containers can also be difficult. We need to remember to delete instances that we don’t need, depending on the flow we’re in.

Imagine we have a login flow that consists of one activity (LoginActivity) and multiple fragments (LoginUsernameFragment and LoginPasswordFragment). These views want to:

  • Access the same LoginUserData instance that needs to be shared until the login flow finishes.
  • Create a new instance of LoginUserData when the flow starts again.

We can achieve that with a login flow container. This container needs to be created when the login flow starts and removed from memory when the flow ends.

Let’s add a LoginContainer to the example code. We want to be able to create multiple instances of LoginContainer in the app, so instead of making it a singleton, make it a class with the dependencies the login flow needs from the AppContainer.

class LoginContainer(val userRepository: UserRepository) {

    val loginData = LoginUserData()

    val loginViewModelFactory = LoginViewModelFactory(userRepository)
}

// AppContainer contains LoginContainer now
class AppContainer {
    ...
    val userRepository = UserRepository(localDataSource, remoteDataSource)

    // LoginContainer will be null when the user is NOT in the login flow
    var loginContainer: LoginContainer? = null
}

Once we have a container specific to a flow, we have to decide when to create and delete the container instance. Because our login flow is self-contained in an activity (LoginActivity), the activity is the one managing the lifecycle of that container. LoginActivity can create the instance in onCreate() and delete it in onDestroy().

class LoginActivity: Activity() {

    private lateinit var loginViewModel: LoginViewModel
    private lateinit var loginData: LoginUserData
    private lateinit var appContainer: AppContainer


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        appContainer = (application as MyApplication).appContainer

        // Login flow has started. Populate loginContainer in AppContainer
        appContainer.loginContainer = LoginContainer(appContainer.userRepository)

        loginViewModel = appContainer.loginContainer.loginViewModelFactory.create()
        loginData = appContainer.loginContainer.loginData
    }

    override fun onDestroy() {
        // Login flow is finishing
        // Removing the instance of loginContainer in the AppContainer
        appContainer.loginContainer = null
        super.onDestroy()
    }
}

Like LoginActivity, login fragments can access the LoginContainer from AppContainer and use the shared LoginUserData instance.

Because in this case we’re dealing with view lifecycle logic, using lifecycle observation makes sense.

That’s all about in this article.

Related Other Articles / Posts

Conclusion

In this article, we understood about how to apply manual dependency injection in a real android application. Dependency injection is a good technique for creating scalable and testable Android applications. Use containers as a way to share instances of classes in different parts of our application and as a centralized place to create instances of classes using factories. This article reviewed an iterated approach of how we might start using manual dependency injection in our android application.

Thanks for reading! I hope you enjoyed and learned about Manual Dependency Injection (DI) concepts in Android. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Android – An Overview Of Dependency Injection In Android

Hello Readers, CoolMonkTechie heartily welcomes you in this article (An Overview Of Dependency Injection In Android).

In this article, we will learn about Dependency Injection in android. Dependency Injection (DI) is a technique widely used in programming and well suited to Android development. By following the principles of DI, we lay the groundwork for good app architecture. This article explains an overview of how Dependency Injection (DI) works in Android.

A famous quote about learning is :

” Wisdom is not a product of schooling but of the lifelong attempt to acquire it.”

So let’s begin.

An Overview Of Dependency Injection

Dependency injection is based on the Inversion of Control principle in which generic code controls the execution of specific code.

Classes often require references to other classes. For example, a Car class might need a reference to an Engine class. These required classes are called dependencies, and in this example the Car class is dependent on having an instance of the Engine class to run.

There are three ways for a class to get an object it needs:

  1. The class constructs the dependency it needs. In the example above, Car would create and initialize its own instance of Engine.
  2. Grab it from somewhere else. Some Android APIs, such as Context getters and getSystemService(), work this way.
  3. Have it supplied as a parameter. The app can provide these dependencies when the class is constructed or pass them in to the functions that need each dependency. In the example above, the Car constructor would receive Engine as a parameter.

With this Dependency Injection (DI) approach, we take the dependencies of a class and provide them rather than having the class instance obtain them itself.

Example Without Dependency Injection

This example is representing a Car that creates its own Engine dependency in code looks like this:

class Car {

    private val engine = Engine()

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.start()
}

This is not an example of dependency injection because the Car class is constructing its own Engine.

Source : Android Developers – Example Car Engine Without DI

This can be problematic because:

  • Car and Engine are tightly coupled – an instance of Car uses one type of Engine, and no subclasses or alternative implementations can easily be used. If the Car were to construct its own Engine, we would have to create two types of Car instead of just reusing the same Car for engines of type Gas and Electric.
  • The hard dependency on Engine makes testing more difficult. Car uses a real instance of Engine, thus preventing us from using a test double to modify Engine for different test cases.

Example With Dependency Injection

With this example, instead of each instance of Car constructing its own Engine object on initialization, it receives an Engine object as a parameter in its constructor. The code looks like this.

class Car(private val engine: Engine) {
    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val engine = Engine()
    val car = Car(engine)
    car.start()
}

The main function uses Car. Because Car depends on Engine, the application creates an instance of Engine and then uses it to construct an instance of Car.

Source : Android Developers – Example Car Engine With DI

The benefits of this DI-based approach are:

  • Reusability of Car: We can pass in different implementations of Engine to Car. For example, we might define a new subclass of Engine called ElectricEngine that we want Car to use. If we use DI, all we need to do is pass in an instance of the updated ElectricEngine subclass, and Car still works without any further changes.
  • Easy testing of Car: We can pass in test doubles to test our different scenarios. For example, we might create a test double of Engine called FakeEngine and configure it for different tests.

Major Ways to do Dependency Injection

There are two major ways to do dependency injection in Android:

  • Constructor Injection. This is the way described above. We pass the dependencies of a class to its constructor.
  • Field Injection (or Setter Injection). Certain Android framework classes such as activities and fragments are instantiated by the system, so constructor injection is not possible. With field injection, dependencies are instantiated after the class is created. The code would look like this:
class Car {
    lateinit var engine: Engine

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.engine = Engine()
    car.start()
}

Automated Dependency Injection

In the previous example, we created, provided, and managed the dependencies of the different classes ourself, without relying on a library. This is called dependency injection by hand, or manual dependency injection. In the Car example, there was only one dependency, but more dependencies and classes can make manual injection of dependencies more tedious. Manual dependency injection also presents several problems:

  • For big apps, taking all the dependencies and connecting them correctly can require a large amount of boilerplate code. In a multi-layered architecture, in order to create an object for a top layer, we have to provide all the dependencies of the layers below it. As a concrete example, to build a real car we might need an engine, a transmission, a chassis, and other parts; and an engine in turn needs cylinders and spark plugs.
  • When we’re not able to construct dependencies before passing them in — for example when using lazy initializations or scoping objects to flows of our app — we need to write and maintain a custom container (or graph of dependencies) that manages the lifetimes of our dependencies in memory.

Dependency Injection Libraries

There are libraries that solve this problem by automating the process of creating and providing dependencies. They fit into two categories:

  • Reflection-based solutions that connect dependencies at runtime.
  • Static solutions that generate the code to connect dependencies at compile time.

Dagger is a popular dependency injection library for Java, Kotlin, and Android that is maintained by Google. It facilitates using DI in our app by creating and managing the graph of dependencies for us. It provides fully static and compile-time dependencies addressing many of the development and performance issues of reflection-based solutions such as Guice.

Alternatives To Dependency Injection

An alternative to dependency injection is using a service locator. The service locator design pattern also improves decoupling of classes from concrete dependencies. We create a class known as the service locator that creates and stores dependencies and then provides those dependencies on demand.

object ServiceLocator {
    fun getEngine(): Engine = Engine()
}

class Car {
    private val engine = ServiceLocator.getEngine()

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.start()
}

Dependency Injection Vs Service Locator Pattern

The service locator pattern is different from dependency injection in the way the elements are consumed. With the service locator pattern, classes have control and ask for objects to be injected; with dependency injection, the app has control and proactively injects the required objects.

The Comparisons between dependency injection and Service Locator Pattern are:

  • The collection of dependencies required by a service locator makes code harder to test because all the tests have to interact with the same global service locator.
  • Dependencies are encoded in the class implementation, not in the API surface. As a result, it’s harder to know what a class needs from the outside. As a result, changes to Car or the dependencies available in the service locator might result in runtime or test failures by causing references to fail.
  • Managing lifetimes of objects is more difficult if we want to scope to anything other than the lifetime of the entire app.

Use Hilt in Android Application

Hilt is Jetpack’s recommended library for dependency injection in Android. It defines a standard way to do DI in our application by providing containers for every Android class in our project and managing their lifecycles automatically for us.

Hilt is built on top of the popular DI library Dagger to benefit from the compile time correctness, runtime performance, scalability, and Android Studio support that Dagger provides.

Benefits Of Dependency Injection

Dependency injection provides our app with the following advantages:

  • Reusability of classes and decoupling of dependencies: It’s easier to swap out implementations of a dependency. Code reuse is improved because of inversion of control, and classes no longer control how their dependencies are created, but instead work with any configuration.
  • Ease of refactoring: The dependencies become a verifiable part of the API surface, so they can be checked at object-creation time or at compile time rather than being hidden as implementation details.
  • Ease of testing: A class doesn’t manage its dependencies, so when you’re testing it, you can pass in different implementations to test all of your different cases.

That’s all about in this article.

Related Other Articles / Posts

Conclusion

In this article, we understood about Dependency Injection fundamental in android. This article reviewed an overview of how Dependency Injection (DI) works in Android.

Thanks for reading! I hope you enjoyed and learned about DI concepts in Android. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Exit mobile version