Fragment

Examples to explain the behaviour of a fragment with and without addToBackStack

Fragment and Activity lifecycle

images.jpeg

Option 1 – “addToBackStack()” is never used

Case 1A – adding, removing, and clicking Back button

Activity :      onCreate() - onStart() - onResume()                             Activity is visible
add Fragment A :    onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment A is visible
add Fragment B :    onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment B is visible
add Fragment C :    onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment C is visible
remove Fragment C :     onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               Fragment B is visible
(Back button clicked)
Activity :      onPause() - onStop() - onDestroy()
Fragment A :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()
Fragment B :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               App is closed, nothing is visible

Case 1B – adding, replacing, and clicking Back button

Activity :      onCreate() - onStart() - onResume()                             Activity is visible
add Fragment A :    onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment A is visible
add Fragment B :    onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment B is visible
(replace Fragment C)    
Fragment B :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               
Fragment A :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()
Fragment C :        onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment C is visible
(Back button clicked)
Activity :      onPause() - onStop() - onDestroy()
Fragment C :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               App is closed, nothing is visible

Option 2 – “addToBackStack()” is always used

Case 2A – adding, removing, and clicking Back button

Activity :      onCreate() - onStart() - onResume()                             Activity is visible
add Fragment A :    onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment A is visible
add Fragment B :    onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment B is visible
add Fragment C :    onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment C is visible
remove Fragment C :     onPause() - onStop() - onDestroyView()                              Fragment B is visible
(Back button clicked)
Fragment C :        onCreateView() - onActivityCreated() - onStart() - onResume()                   Fragment C is visible
(Back button clicked)
Fragment C :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               Fragment B is visible
(Back button clicked)
Fragment B :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               Fragment A is visible
(Back button clicked)
Fragment A :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               Activity is visible
(Back button clicked)
Activity :      onPause() - onStop() - onDestroy()                              App is closed, nothing is visible

Case 2B – adding, replacing, removing, and clicking Back button

Activity :      onCreate() - onStart() - onResume()                             Activity is visible
add Fragment A :    onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment A is visible
add Fragment B :    onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment B is visible
(replace Fragment C)    
Fragment B :        onPause() - onStop() - onDestroyView()  
Fragment A :        onPause() - onStop() - onDestroyView() 
Fragment C :        onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment C is visible
remove Fragment C :     onPause() - onStop() - onDestroyView()                              Activity is visible
(Back button clicked)
Fragment C :        onCreateView() - onActivityCreated() - onStart() - onResume()                   Fragment C is visible
(Back button clicked)
Fragment C :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               
Fragment A :        onCreateView() - onActivityCreated() - onStart() - onResume()   
Fragment B :        onCreateView() - onActivityCreated() - onStart() - onResume()                   Fragment B is visible
(Back button clicked)
Fragment B :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               Fragment A is visible
(Back button clicked)
Fragment A :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               Activity is visible
(Back button clicked)
Activity :      onPause() - onStop() - onDestroy()                              App is closed, nothing is visible

Option 3 – “addToBackStack()” is not used always (in the below examples, w/o indicates that it is not used)

Case 3A – adding, removing, and clicking Back button

Activity :      onCreate() - onStart() - onResume()                             Activity is visible
add Fragment A :    onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment A is visible
add Fragment B w/o:     onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment B is visible
add Fragment C w/o:     onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment C is visible
remove Fragment C :     onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               Fragment B is visible
(Back button clicked)
Fragment B :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               
Fragment A :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               Activity is visible
(Back button clicked)
Activity :      onPause() - onStop() - onDestroy()                              App is closed, nothing is visible

Case 3B – adding, replacing, removing, and clicking Back button

Activity :      onCreate() - onStart() - onResume()                             Activity is visible
add Fragment A :    onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment A is visible
add Fragment B w/o:     onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment B is visible
(replace Fragment C)    
Fragment B :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()   
Fragment A :        onPause() - onStop() - onDestroyView() 
Fragment C :        onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()     Fragment C is visible
remove Fragment C :     onPause() - onStop() - onDestroyView()                              Activity is visible
(Back button clicked)
Fragment C :        onCreateView() - onActivityCreated() - onStart() - onResume()                   Fragment C is visible
(Back button clicked)
Fragment C :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               
Fragment A :        onCreateView() - onActivityCreated() - onStart() - onResume()                   Fragment A is visible
(Back button clicked)
Fragment A :        onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               Activity is visible
(Back button clicked)
Activity :      onPause() - onStop() - onDestroy()                              App is closed, nothing is visible
OnBackPressedCallback(true) this will be block the user and you can handle action here
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
    override fun handleOnBackPressed() {
        // in here you can do logic when backPress is clicked
        if(BuildConfig.DEBUG) {
            Log.i("TAG", " back pressed is blocked.")
        }
    }
})

OnBackPressedCallback(false) this will be not block the user and you can handle action here
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(false) {
    override fun handleOnBackPressed() {
        // in here you can do logic when backPress is clicked
        if(BuildConfig.DEBUG) {
            Log.i("TAG", " back pressed is not blocked.")
        }
    }
})
By navalkishorjha

Thread & Coroutines

Usually, instances of classes that implement the Runnable interface is used to create threads in Kotlin. Because of the Runnable interface has just one method, the run() method, you can leverage Kotlin’s feature to create new threads with minimal boilerplate code.

Here’s how you can use the thread() function, which is a part of Kotlin’s standard library, to quickly create and start a new thread:

thread {
// long running operation goes here
}

The above approach is appropriate only when you need to occasionally spawn a thread or two.

If concurrency is an important part of your app’s business logic and you need a large number of threads, using thread pools with an executor service is a better idea.

For example, the following code uses the newFixedThreadPool() method of the Executors class to create a thread pool containing eight reusable threads and runs a large number of background operations on it:

val myService:ExecutorService = Executors.newFixedThreadPool(8)
var i = 0
 
while (i < items.size) {  // items may be a large array
val item = items[i]
myService.submit {
processItem(item) // a long running operation
}
 
i += 1
}

It might not be obvious at first glance but, in the above code, the argument to the submit() method of the executor service is actually a Runnable object.

Background tasks created using the Runnable interface cannot return any results directly. If you want to receive results from your threads, you must use the Callable interface instead, which is also a SAM interface.

When you pass a Callable object to the submit() method of an executor service, you receive a Future object. As its name suggests, the Future object will contain the result of the Callable at some point in the future, when the executor service has finished running it. To get the actual result from a Future object, all you need to do is call its get() method—but beware, your thread will block if you call it prematurely.

The following sample code shows you how to create a Callable object that returns a Future of type String, run it, and print its result:

val myService:ExecutorService = Executors.newFixedThreadPool(2)
 
val result = myService.submit(Callable {
// some background operation that generates
// a string
})
 
// Other operations
 
// Print result
Log.d(TAG, result.get())

Unlike Java, Kotlin doesn’t have the synchronized keyword. Therefore, to synchronize multiple background operations, you are expected to use either the @Synchronized annotation or the synchronized() standard library inline function. The annotation can synchronize an entire method, and the function works on a block of statements.

// a synchronized function
@Synchronized fun myFunction() {
 
}
 
fun myOtherFunction() {
 
// a synchronized block
synchronized(this) {
 
}
}

Both the @Synchronized annotation and the synchronized() function use the concept of monitor locks.

If you don’t already know, every object on the JVM has a monitor associated with it. For now, you can think of a monitor as a special token that a thread can acquire, or lock, to gain exclusive access to the object. Once an object’s monitor is locked, other threads that want to work on the object will have to wait until the monitor is released, or unlocked, again.

While the @Synchronized annotation locks the monitor of the object the associated method belongs to, the synchronized() function can lock the monitor of any object that’s passed to it as an argument.

Kotlin offers an alternative approach to achieve concurrency: coroutines. Coroutines are far lighter than threads and are much easier to manage.

In mobile multithreaded applications, threads are usually used for operations such as fetching information from the Internet or querying databases. Such operations don’t involve much computation, and the threads spend most of their lifetime in a blocked state, just waiting for data to come from somewhere else. As you can probably tell, that’s not a very efficient way to use the CPU.

Coroutines are designed to be used instead of threads for such operations. The most important thing you need to understand about coroutines is that they are suspendable. In other words, instead of blocking, they can simply stop when necessary and seamlessly continue later. This leads to much better CPU utilization. Indeed, with well-designed coroutines, you can effortlessly run dozens of background operations.

To be able to use coroutines in your Android Studio project, make sure you add the following compile dependency in the app module’s build.gradle file:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'

A coroutine can be suspended only with the help of a suspending function. Therefore, most coroutines have calls to at least one such function inside them.

To create a suspending function, all you need to do is add the suspend modifier to a regular function. Here’s a typical suspending function executing an HTTP GET request using the khttp library:

suspend fun fetchWebsiteContents(url: String):String {
return khttp.get(url).text
}

Note that a suspending function can be called only by a coroutine or another suspending function. If you try calling it from anywhere else, your code will fail to compile.

When it comes to creating a new coroutine, Kotlin’s standard library has enough coroutine builders to make you feel spoiled for choice. The simplest coroutine builder you can use is the launch() function, and like most other coroutine builders, it expects a suspending lambda, which is nothing but an anonymous suspending function. As such, this lambda is what becomes the coroutine.

The following code creates a coroutine that makes two sequential calls to the suspending function we created in the previous step:

val job1 = launch {
val url1 = fetchWebsiteContents("https://code.tutsplus.com")
val url2 = fetchWebsiteContents("https://design.tutsplus.com")
}

The return value of the launch() function is a Job object, which you can use to manage the coroutine. For example, you can call its join() method to wait for the coroutine to complete. Similarly, you can call its cancel() method to immediately cancel the coroutine.

Using the launch() function is much like creating a new thread with a Runnable object, primarily because you can’t return any value from it. If you want to be able to return a value from your coroutine, you must create it using the async() function instead.

The async() function returns a Deferred object, which, just like the Job object, allows you to manage the coroutine. However, it also allows you to use the await() function to wait for the result of the coroutine without blocking the current thread.

For instance, consider the following coroutines that use the fetchWebsiteContents() suspending function and return the content lengths of two different webpage addresses:

val jobForLength1 = async {
fetchWebsiteContents("https://webdesign.tutsplus.com").length
}
 
val jobForLength2 = async {
fetchWebsiteContents("https://photography.tutsplus.com").length
}

With the above code, both the coroutines will start immediately and run in parallel.

If you now want to use the returned lengths, you must call the await() method on both the Deferred objects. However, because the await() method too is a suspending function, you must make sure that you call it from another coroutine.

The following code shows you how to calculate the sum of the two lengths using a new coroutine created with the launch() function:

launch {
val sum = jobForLength1.await() + jobForLength2.await()
println("Downloaded $sum bytes!")
}

Coroutines do make use of background threads internally, which is why they don’t run on an Android app’s UI thread by default. Consequently, if you try modifying the contents of your app’s user interface from inside a coroutine, you will encounter a runtime error. Fortunately, it’s trivially easy to run a coroutine on the UI thread: you just have to pass the UI object as an argument to your coroutine builder.

For example, here’s how you can rewrite the last coroutine to display the sum inside a TextView widget:

launch(UI) {
val sum = jobForLength1.await() + jobForLength2.await()
myTextView.text = "Downloaded $sum bytes!"
}

The above code might seem mundane at first, but look again. It’s not only able to wait for two background operations to complete without using callbacks, it’s able to do so on the app’s UI thread without blocking it!

Having the ability to wait on the UI thread, without making your UI feel sluggish or triggering an Application Not Responding error, often referred to as ANR, simplifies a lot of otherwise complex tasks.

For instance, with the suspending delay() function, which is the non-blocking equivalent of the Thread.sleep() method, you can now create animations with loops. To help you get started, here’s a sample coroutine that increments the x coordinate of a TextView widget every 400 ms, thus creating a marquee-like effect:

launch(UI) {   
    while(myTextView.x < 800) {
        myTextView.x += 10
        delay(400)
    }
}

Always launch coroutines on the UI layer of your app (ViewModel, Activity, or Fragment) and tie them to its lifecycle by using the appropriate CoroutineScope.

Every other async function can simply be marked with suspend, to indicate that it needs to be called from a coroutine without actually creating one.

1. ViewModel

When launching coroutines from a ViewModel you can use viewModelScope

viewModelScope.launch {
// Coroutine ...
}

2. Activity

When launching coroutines from an Activity you can use lifecycleScope

lifecycleScope.launch {
// Coroutine ...
}

3. Fragment

When launching coroutines from a Fragment you can use viewLifecycleOwner.lifecycleScope

viewLifecycleOwner.lifecycleScope.launch {
// Coroutine ...
}

It is important to access the lifecycle scope of the viewLifecycleOwner to tie the coroutine to the Fragment’s View, instead of the Fragment itself.

4. App-wide Coroutines

Sometimes you might want a coroutine to complete regardless of its creator lifecycle. Some examples can be:

  • Syncing data between your server and the local storage.
  • Executing fire-and-forget requests to the backend.
  • Running periodic tasks.
  • Communicating with external services.

For those and other scenarios there are two solutions:

1. WorkManager
WorkManager is a Jetpack library that makes it easy to schedule deferrable and asynchronous tasks that are expected to run even if the app exits or the device restarts.

Implement the CoroutineWorker class on your Workers to gain access to the suspend function doWork().

Workers are queued using the WorkManager and therefore become independent to the current lifecycle.

You can learn more about WorkManager on the Android Developers Documentation.

2. Application Coroutine Scope
Create a new coroutine scope inside the Application class and inject it into the components that need it.

val appScope = CoroutineScope(SupervisorJob())

For this scope, ensure you create a SupervisorJob instead of a Job, to prevent cancelling all child coroutines when one fails.

This solution works well for simple one-off operations. For more complex or recurring tasks, however, use the WorkManager.

CoroutineContext: Choosing the right thread

Coroutines not only execute tasks in the background, but they can also run on the Main Thread.

The element that determines which Thread the coroutine uses is called CoroutineContext.

All coroutine builders accept an optional CoroutineContext parameter. The context (and thread) can also be changed during the coroutine execution by using the withContext(context) { … } block.

The easiest way to specify a coroutine context is by using the built-in Dispatchers class.
There are 3 Dispatchers you can use:

  • Main: For Main Thread operations.
  • IO: For Background operations that need to wait.
  • Default: For Background CPU-intensive tasks, where there is little or no sleep.

Example: To launch a coroutine that retrieves some data from an API and then updates a local list, launch the coroutine with the IO Dispatcher.
After receiving the response, switch back to the Main Thread to update the UI using the Main Dispatcher.

scope : above any 1,2 3 type is mentioned based on that scope can define.

scope.launch(Dispatchers.IO) {
val result = apiService.getListData()
withContext(Dispatchers.Main) {
adapter.submitList(result)
textview.text="hello"
}
}

This is very similar to launching a new Thread or AsyncTask and then using runOnUiThread { … } to update the UI.

Most 3rd party services like Ktor and Retrofit already support this behavior automatically by using the IO Dispatcher on all requests. In those cases, you can safely launch the coroutine on your current scope without specifying any context.

Testing Tip: Consider injecting Dispatchers instead of hardcoding them. This will help with Unit Testing your classes by using the TestCoroutineDispatcher() as your dispatcher for tests, to execute all coroutines synchronously.

 

Running coroutines sequentially

Running tasks sequentially means that we want to wait for each one to complete before moving to the next one. This is needed for example if the result of one task is used by the next one. For this purpose, withContext() is the ideal solution.

 





(1) is executed first and the first withContext() doesn’t return until (1) completes.
When (1) completes, we have the result in result1 and the second withContext() is executed. (2) can use the value available in result1 if needed. Again, given that withContext() is a suspend function, the parent coroutine is suspended until (2) completes. At that point, result2 will be available within the parent coroutine to continue with other operations.

Running coroutines in parallel

Running tasks in parallel means that they can run at the same time on separate threads making use of the multiple CPU cores available. This results in faster processing time because we don’t need to wait for one task to complete before moving to the next one. This is typically possible when the result of one task is not used by the next one. A good candidate to achieve this behaviour in the coroutines world is for sure async().

 

While developing Android apps, it is imperative that you perform long-running operations in background threads. In this tutorial, you learned several approaches you can follow to create and manage such threads in Kotlin. You also learned how to use the still experimental coroutines feature to wait inside threads without blocking them.

To learn more about coroutines, you can refer to the official documentation.

By navalkishorjha