Here, routes are all our routes kept in a separate file. The scrollBehavior()function is what manages the scrolling of our routes. It has 3 parameters:
to — This represents the new route we will be visiting
from — This is the previous route we came from. So if we click on a <router-link> on Home page to visit the About page, then to would be our About page and from is the Home page.
savedPosition — This is the important parameter. It represents the previous position before scrolling. It will become clear after I explain what the above function does.
DSL stands for Domain Specefic Language, which is a type of programming language that cares mainly about the readability by enforcing the use of declarative code with minimal boilerprate (declare to use) instead of imperative code (explaining how to solve a problem) but as a drawback, it can be used only inside a specific context or domain.
getChildFragmentManager is associated with fragments.
Whenever you are ViewPager inside a fragment you will use getChildFragmentManager.
Example:
1 2
FragmentManager cfManager=getChildFragmentManager(); viewPagerAdapter = new ViewPagerAdapter(cfManager);
Here is the official link for this for better understanding.
When it comes to common mistakes people make when they are using ViewPager inside a Fragment, they often pass getSupportFragmentManager, which is a fragment manager for an activity, and it causes issues such as memory leaks or ViewPager not updating properly.
The most important issue caused by using getSupportFragmentManager in fragments is a memory leak. But why does it happen? Well, you have a stack of fragments which are used by ViewPager, and all these fragments stack in activity since you used getSupportFragmentManager. Now if close your Parent fragment, it will be closed but it will not be destroyed because all child fragments are active and they are still in memory, hence causing leak. It will not just leak parent fragment but also all of the child fragments since none of them can be cleared from heap memory. So never try to use getSupportFragmentManager in a fragment
Callback from DialogFragment, ChildFragment, BottomSheetFragment to a Parent Fragment
This is a very common issue people face when they use BottomSheetFragment or DialogFragment or ChildFragment.
Example:
Add a child fragment:
Another example bottomSheetFragment:
1 2
BottomSheetDialogFragment fragment = BottomSheetDialogFragment.newInstance(); fragment.show(getChildFragmentManager(), fragment.getTag());
Now suppose you want a callback from these child fragments to parent fragments. Most people create connections between two fragments using an activity, few people pass interface listeners as a parameter to fragments (which is a really a bad practice that one should avoid). The best way to call getParentFragment() from your child fragment is to create a callback. This is very simple. Consider the example below:
Then set the callback to the parent fragment by adding the following code in the child fragment:
That’s it. You can give a callback to your parent fragment now easily.
Using the same method, you can create a callback from a child fragment inside ViewPager to the parent fragment who is holding ViewPager.
Fragments When Using ViewPager and When to Use FragmentStateAdapter vs FragmentPagerAdapter
FragmentPagerAdapter stores the whole fragment in memory and can cause an increase of memory overhead if a large number of fragments are used in ViewPager. FragmentStatePagerAdapter only stores the savedInstanceState of fragments and destroys all the fragments when they lose focus.
So when you are going to have many fragments, use FragmentStateAdapter. If ViewPager is going to have less than three fragments, use FragmentPagerAdapter.
Let’s look at some commonly faced issues.
Update ViewPager not working:
Remember ViewPager fragments are managed by FragmentManager, either from fragment or activity, and FragmentManager holds instances of all ViewPager fragments.
So when people say ViewPager is not refreshed, it’s nothing but old instances of fragments are still being held by FragmentManager. You need to find out why FragmentManger is holding an instance of fragments. Is there a leak or not? Ideally, to refresh ViewPager the following code works. If it is not, you are doing something wrong.
This is also a very common issue we come across. If you come across this, either create an array list of fragments inside the adapter or try to access the fragment using some tags. I prefer another option, however. FragmentStateAdapter and FragmentPagerAdapter both provide the method setPrimaryItem. This can be used to set the current fragment as seen below:
I am leaving a GitHub link to this simple ViewPager project so that everyone can understand better.
In our activity, we have a container with our fragments displayed inside.
Add will simply add a fragment to the container. Suppose you add FragmentA and FragmentB to the container. The container will have FragmentA and FragmentB and if the container is FrameLayout, fragments are added one above the other.
Replace will simply replace a fragment on top of the container, so if I call create FragmentC and call replace FragmentB which was on top, FragmentB will be removed from the container (unless you are not calling addToBackStack) and now FragmentC will be on top.
So which one to use when? replace removes the existing fragment and adds a new fragment. This means when you press the back button, the fragment that got replaced will be created with its onCreateView being invoked. On the other hand, add retains the existing fragments and adds a new fragment that means existing fragment will be active and they wont be in ‘paused’ state. Hence, when a back button is pressed onCreateView, it is not called for the existing fragment (the fragment which was there before the new fragment was added). In terms of fragment’s life cycle events, onPause, onResume, onCreateView, and other life cycle events will be invoked in case of replace but not in the case of add.
Use replace fragment if don’t need to revisit current fragments and current fragments are not required anymore. Also if your app has memory constraints, consider using replace instead of add.
Fragment Receivers, Broadcasts and Memory Leaks
When using receivers inside a fragment, a common mistake is to forget to unregister the receiver in onPause or OnDestroy. If you are registering a fragment to listen to the receiver inside onCreate or OnResume, you will have to unregister it inside onPause or onDestroy. Otherwise, it will cause a memory leak.
Also, if have multiple fragments listening to the same broadcast receiver, make sure you register in onResume and unregister in onPause. If you use onCreate and onDestroy to register and unregister, other fragments will not receive the broadcast as this fragment is not destroyed
How to Handle Fragment BottomBarNavigation and NavigationDrawer
When we are using BottomBarNavigation and NavigationDrawer, it’s common to see issues such as fragments being recreated or the same fragment being added multiple times.
In such a case, you can use the fragment transactions show and hide instead of add or replace.
There is also a beautiful library which takes care of navigations and avoids recreation of fragments called FragNav. I’ve linked it below.
If your activity is not in a resume state and you try to commit a fragment, your app will crash. To avoid this, you need to check if the activity or fragment is in a resume state or not isAdded() / isResumed()
Another solution, if you don’t care much about the state of the fragment, is that you can call commitAllowingStateLoss. This ensures the fragments are added or replaced, despite the activity is finishing or not being in a resume state.
Fragment Option Menus
When using option menu inside the fragment, remember to add the following line. People often forgot to add this and keep wondering where the option is on the toolbar.
1 2 3 4 5
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); }
When using the toolbar inside the fragment, you can inflate the menu using code:
Alternatively, you can override on createOptionsMenu but I prefer the above method as it does not rely on a super class
Fragment getActivity(), getView() NullPointers Exceptions
If any background process posts a result and the fragment is not in the stack or in a resume state, accessing the view of the fragment will cause a NullPointer exception. So, whenever you are accessing getView or getActivity after a background operation or delay, make sure you cancel all background operations on termination.
Example:
Nested Fragment onActivityResult
Yes, the onActivityResult() in a nested fragment will not be invoked.
The calling sequence of onActivityResult (in Android support library) is
Activity.dispatchActivityResult().
FragmentActivity.onActivityResult().
Fragment.onActivityResult().
You will have to use onActivityResult() in parent fragments or activity and to pass the result to a nested fragment as below:
Fragment and Bundle
Whenever you are passing arguments to a fragment, make sure you are using Bundle instead of a constructor.
Every fragment must have an empty constructor, so it can be instantiated when restoring its activity’s state. It is strongly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments(Bundle) and later retrieved by the Fragment with getArguments().
That’s why it’s better to use a bundle to set the parameters of the fragment, it’s easier for the system to restore its values when the fragment is re-instantiated.
Back Navigation
You should ensure that pressing the Back button on a detail screen returns the user to the master screen. To do so, call addToBackStack() before you commit the transaction:
When there are FragmentTransaction objects on the back stack and the user presses the Back button, the FragmentManager pops the most recent transaction off the back stack and performs the reverse action (such as removing a fragment if the transaction added it).
Conclusion
Fragments can seem pretty easy at first but there is a lot more to them. You need to be careful of a number of things while using fragments, such as memory, navigation, callbacks, and bundle. I hope most commonly faced issues and most commonly made mistakes were covered in this article.
UPDATE mysql.user SET authentication_string = PASSWORD('123456'), password_expired = 'N', plugin = 'mysql_native_password' WHERE User = 'root' AND Host = 'localhost';
Part one and two of this series focused on how coroutines can be used to simplify code, provide main-safety on Android, and avoid leaking work. With that background, they look like a great solution to both background processing and a way to simplify callback based code on Android.
So far, we’ve focused on what coroutines are and how to manage them. In this post we’ll look at how to use them to accomplish some real tasks. Coroutines are a general purpose programming language feature at the same level as functions — so you can use them to implement anything that you could with functions and objects. However, there’s a two types of tasks that come up all the time in real code that coroutines are a great solution for:
One shotrequests are requests that are run each time they are called — they always complete after the result is ready.
Streamingrequests are requests that continue to observe changes and report them to caller — they don’t complete when the first result is ready.
Coroutines are a great solution to both of these tasks. In this post, we’ll look deeply into one shot requests and explore how to implement them using coroutines on Android.
One shot requests
A one shot request is performed once each time it’s called and completes as soon as a result is ready. This pattern is the same as a regular function call — it gets called, does some work, then returns. Due to the similarity to function calls they tend to be easier to understand than streaming requests.
A one shot request is performed each time it’s called. It stops executing as soon as a result is ready.
For an example of a one shot request, consider how your browser loaded this page. When you clicked the link to this post your browser sent a network request to the server to load the page. Once the page was transferred to your browser it stopped talking to the backend — it had all the data it needed. If the server modified the post, the new changes would not be shown in your browser — you would have to refresh the page.
So, while they lack the live-push of streaming requests, one shot requests are pretty powerful. There’s a whole lot of things you can do in an Android app that can be solved by one shot requests like fetching, storing, or updating data. It’s also a good pattern for things like sorting a list.
Problem: Displaying a sorted a list
Let’s explore one-shot requests by looking at how you might display a sorted list. To make the example concrete, let’s build an inventory app for use by an employee at a store. It will be used to lookup products based on when they were last stocked — they’ll want to be able to sort the list both ascending and descending. It has so many products that sorting it may take almost a second — so we’ll use coroutines to avoid blocking the main thread!
In this app all of the products are stored in a Room database. This is a good use case to explore since it doesn’t need to involve a network request so we can focus on the pattern. Even though the example is simpler because it doesn’t use the network, it exposes the patterns needed to implement one shot requests.
To implement this request using coroutines, you will introduce coroutines to the ViewModel, Repository, and Dao. Lets walk through each one at a time and see how to integrate them with coroutines.
/** * Called by the UI when the user clicks the appropriate sort button */ funonSortAscending() = sortPricesBy(ascending = true) funonSortDescending() = sortPricesBy(ascending = false)
privatefunsortPricesBy(ascending: Boolean) { viewModelScope.launch { // suspend and resume make this database request main-safe // so our ViewModel doesn't need to worry about threading _sortedProducts.value = productsRepository.loadSortedProducts(ascending) } } }
ProductsViewModel is responsible for receiving events from the UI layer, then asking the repository for the updated data. It uses LiveData to hold the currently sorted list to for display by the UI. When a new event comes in sortProductsBy starts a new coroutine to sort the list and updates the LiveData when the result is ready. The ViewModel is typically the right place to start most coroutines in this architecture, since it can cancel the coroutine in onCleared. If the user leaves the screen they usually have no use for outstanding work.
If you haven’t used LiveData much, check out this great post by@CeruleanOtterintroducing how they work to store data for UIs.
This is a general pattern for coroutines on Android. Since the Android framework doesn’t call suspend functions, you’ll need to coordinate with a coroutine in response to a UI event. The easiest way to do that is to just start a new coroutine when the event comes in — and the natural place to do that is in the ViewModel.
As a general pattern, start coroutines in the ViewModel.
The ViewModel uses a ProductsRepository to actually fetch the data. Here’s what that looks like:
/** * This is a "regular" suspending function, which means the caller must * be in a coroutine. The repository is not responsible for starting or * stopping coroutines since it doesn't have a natural lifecycle to cancel * unnecessary work. * * This *may* be called from Dispatchers.Main and is main-safe because * Room will take care of main-safety for us. */ suspendfunloadSortedProducts(ascending: Boolean): List<ProductListing> { returnif (ascending) { productsDao.loadProductsByDateStockedAscending() } else { productsDao.loadProductsByDateStockedDescending() } } }
ProductsRepository provides a reasonable interface for interacting with products. In this app, since everything is in the local Room database, it just provides a nice interface for the @Dao that has two different functions for the different sort orders.
The repository is an optional part of the Android Architecture Components architecture — but if you do have it or a similar layer in your app, it should prefer to expose regular suspend functions. Since a repository doesn’t have a natural lifecycle — it’s just an object — it would have no way to cleanup work. As a result, any coroutines started in the repository will leak by default.
In addition to avoiding leaks, by exposing regular suspend functions, it’s easy to re-use the repository in different contexts. Anything that knows how to make a coroutine can call loadSortedProducts. For example, a background job scheduled by the WorkManager library could call this directly.
A repository should prefer to expose regular suspend functions that are main-safe.
Note: Some background save operations may want to continue after user leaves a screen — and it makes sense to have those saves run without a lifecycle. In most other cases the*viewModelScope*is a reasonable choice.
Moving on to ProductsDao, it looks like this:
1 2 3 4 5 6 7 8 9 10 11 12
@Dao interfaceProductsDao { // Because this is marked suspend, Room will use it's own dispatcher // to run this query in a main-safe way. @Query("select * from ProductListing ORDER BY dateStocked ASC") suspendfunloadProductsByDateStockedAscending(): List<ProductListing>
// Because this is marked suspend, Room will use it's own dispatcher // to run this query in a main-safe way. @Query("select * from ProductListing ORDER BY dateStocked DESC") suspendfunloadProductsByDateStockedDescending(): List<ProductListing> }
ProductsDao is a Room @Dao that exposes two suspend functions. Because the functions are marked suspend, Room ensures they are main-safe. That means you can call them directly from Dispatchers.Main.
If you haven’t seen coroutines in Room yet, check out this great post by*@*FMuntenescu
A bit of warning though, the coroutine that calls this will be on the main thread. So if you did something expensive with the results — like transforming them to a new list — you should make sure you’re not blocking the main thread.
Note:Room uses its own dispatcher to run queries on a background thread. Your code should not use*withContext(Dispatchers.IO)*to call suspending room queries. It will complicate the code and make your queries run slower.
Suspend functions in Room are main-safe and run on a custom dispatcher.
The one shot request pattern
That’s the complete pattern for making a one shot request using coroutines in Android Architecture Components. We added coroutines to the ViewModel, Repository, and Room and each layer has a different responsibility.
ViewModel launches a coroutine on the main thread — it completes when it has a result.
Repository exposes regular suspend functions and ensures they are main-safe.
The database and network expose regular suspend functions and ensures they are main-safe.
The**ViewModel** is responsible for starting coroutines and ensuring that they get cancelled if the user leaves the screen. It doesn’t do expensive things— instead relying on other layers to do the heavy work. Once it has the result it sends it to the UI using LiveData.
Since the ViewModel doesn’t do heavy work it starts the coroutine on on the main thread. By starting on main it can respond to user events faster if the result is available immediately (e.g. from an in-memory cache).
The**Repository** exposes regular suspend functions to access data. It typically doesn’t start it’s own long lived coroutines since it doesn’t have any way to cancel them. Whenever the Repository has to do expensive things like transform a list it should use withContext to expose a main-safe interface.
The data layer (network or database) always exposes regular suspend functions. It is important that these suspend functions are main-safe when using Kotlin coroutines, and both Room and Retrofit follow this pattern.
In a one shot request, the data layer only exposes suspend functions. A caller has to call them again if they want a new value. This is just like the refresh button on your web browser.
It’s worth taking a moment to make sure you understand these patterns for one shot requests. It’s the normal pattern for coroutines on Android, and you’ll use it all of the time.
Our first bug report!
After testing that solution, you launch it to production and everything is going well for weeks until you get a really strange bug report:
Subject: 🐞 — wrong sort order!
Report: When I click the sort order buttons really really really really quickly, sometimes the sort is wrong. This doesn’t happen all the time 🙃.
You take a look and scratch your head. What could possibly go wrong? The algorithm seems fairly simple:
Start the sort the user requested.
Run the sort in the Room dispatcher.
Show the result of the sort.
You’re tempted to close the bug **“wontfix — don’t press the buttons so fast”**but you’re worried something may be broken. After adding logging statements and writing a test to call lots of sorts at once— you finally figure it out!
It turns out the result shown isn’t actually the “result of the sort,” it’s actually the result of the “last sort to complete.” When the user spams the button — they start multiple sorts at the same time and they can finish in any order!
When starting a new coroutine in response to a UI event, consider what happens if the user starts another before this one completes.
This is a concurrency bug and it doesn’t really have anything to do with coroutines. We’d have the same bug if we used callbacks, Rx, or even an ExecutorService the same way.
There are many many ways to fix this in both the ViewModel and the Repository. Let’s explore some patterns for ensuring that one shot requests complete in the order the user expects.
The best solution: Disable the button
The fundamental problem is that we’re doing two sorts. We can fix that by making it only do one sort! The easiest way to do that is to disable the sort buttons to stop the new events.
This may seem like a simple solution, but it’s a really good idea. The code to implement this is simple, easy to test, and as long as it makes sense in the UI it’ll completely fix the problem!
To disable the buttons, tell the UI that a sort request is happening inside of sortPricesBy like this:
/** * Called by the UI when the user clicks the appropriate sort button */ funonSortAscending() = sortPricesBy(ascending = true) funonSortDescending() = sortPricesBy(ascending = false)
privatefunsortPricesBy(ascending: Boolean) { viewModelScope.launch { // disable the sort buttons whenever a sort is running _sortButtonsEnabled.value = false try { _sortedProducts.value = productsRepository.loadSortedProducts(ascending) } finally { // re-enable the sort buttons after the sort is complete _sortButtonsEnabled.value = true } } } }
Disabling the buttons while a sort runs using _sortButtonsEnabled in sortPricesBy.
Ok that one was not too bad. Just disable the buttons inside of sortPricesByaround the call to the repository.
And in most cases it’s the right way to fix this problem. But what if we wanted to leave the buttons enabled and fix the bug? That’s a bit harder, and we’ll spend the rest of this post exploring a few different options.
Important: This code shows a major advantage of starting on main — the buttons disable instantly in response to a click. If you switched dispatchers, a fast-fingered user on a slow phone could send more than one click!
Concurrency patterns
The next few sections explore advanced topics — and if you’re just starting with coroutines you don’t need to understand them right away. Simply disabling the button is the best solution to most problems you’ll run across.
For the rest of this post, we’ll explore ways to use coroutines to leave the button enabled but ensure that one shot requests are executed in an order that doesn’t surprise the user. We can do that by avoiding accidental concurrency by controlling when the coroutines run (or don’t run).
There are three basic patterns that you can use for a one shot request to ensure that exactly one request runs at a time.
Cancel previous work before starting more.
Queue the next work and wait for the previous requests to complete before starting another one.
Join previous work if there’s already a request running just return that one instead of starting another request.
As you look through these solutions you’ll notice that they have some complexity to their implementations. To focus in on how to use these patterns instead of implementation details I’ve created a gist with implementations of all three patterns as reusable abstractions.
Solution #1: Cancel the previous work
In the case of sorting, getting a new event from the user often means you can cancel the last sort. After all, what’s the point of continuing if the user has already told you they don’t want the result?
To cancel the previous request, we’ll need to keep track of it somehow. The function cancelPreviousThenRunin the gist does exactly that.
Lets take a look at how it can be used to fix the bug:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// Solution #1: Cancel previous work
// This is a great solution for tasks like sorting and filtering that // can be cancelled if a new request comes in.
classProductsRepository(val productsDao: ProductsDao, val productsApi: ProductsService) { var controlledRunner = ControlledRunner<List<ProductListing>>()
suspendfunloadSortedProducts(ascending: Boolean): List<ProductListing> { // cancel the previous sorts before starting a new one return controlledRunner.cancelPreviousThenRun { if (ascending) { productsDao.loadProductsByDateStockedAscending() } else { productsDao.loadProductsByDateStockedDescending() } } } }
Using cancelPreviousThenRun to ensure that only one sort runs at a time.
Looking at the example implementation for cancelPreviousThenRun in the gist is a good way to see how to keep track of in-progress work.
1 2 3 4 5 6 7
// see the complete implementation at // https://gist.github.com/objcode/7ab4e7b1df8acd88696cb0ccecad16f7 suspendfuncancelPreviousThenRun(block: suspend () -> T): T { // If there is an activeTask, cancel it because it's result is no longer needed activeTask?.cancelAndJoin() // ...
In a nutshell it always keeps track of the currently active sort in the member variable activeTask. Whenever a sort starts, it will immediately cancelAndJoin on whatever is currently in activeTask. This has the effect of cancelling any in progress sorts before starting a new one.
It’s a good idea to use abstractions similar to ControlledRunner<T> to encapsulate logic like this it instead of mixing ad-hoc concurrency with application logic.
Consider building abstractions to avoid mixing ad-hoc concurrency patterns with application code.
Important: This pattern is not well suited for use in global singletons, since unrelated callers shouldn’t cancel each other.
Solution #2: Queue the next work
There’s one solution to concurrency bugs that always works.
Just queue up requests so only one thing can happen at a time! Just like a queue or a line at a store, requests will execute one at a time in the order they started.
For this particular problem of sorting, cancelling is probably better than queuing, but it’s worth talking about because it always works.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// Solution #2: Add a Mutex
// Note: This is not optimal for the specific use case of sorting // or filtering but is a good pattern for network saves.
classProductsRepository(val productsDao: ProductsDao, val productsApi: ProductsService) { val singleRunner = SingleRunner()
suspendfunloadSortedProducts(ascending: Boolean): List<ProductListing> { // wait for the previous sort to complete before starting a new one return singleRunner.afterPrevious { if (ascending) { productsDao.loadProductsByDateStockedAscending() } else { productsDao.loadProductsByDateStockedDescending() } } } }
Whenever a new sort comes in, it uses a instance of SingleRunner to ensure that only one sort is running at a time.
It uses a Mutex, which is a single ticket (or lock), that a coroutine must get in order to enter the block. If another coroutine tried while one was running, it would suspend itself until all pending coroutines were done with the Mutex.
A Mutex lets you ensure only one coroutine runs at a time — and they will finish in the order they started.
Solution 3: Join previous work
The third solution to consider is joining the previous work. It’s a good idea if the new request would re-start the exact same work that has already been half completed.
This pattern doesn’t make very much sense with the sort function, but it’s a natural fit for a network fetch that loads data.
For our product inventory app, the user will need a way to fetch a new product inventory from the server. As a simple UI, we’ll provide them with a refresh button that they can press to start a new network request.
Just like the sort buttons, simply disabling the button while the request is running is a complete solution to the problem. But if we didn’t — or couldn’t — do that, we could instead join the existing request.
Lets look at some code using joinPreviousOrRun from the gist for an example of how this might work:
1 2 3 4 5 6 7 8 9 10 11 12 13
classProductsRepository(val productsDao: ProductsDao, val productsApi: ProductsService) { var controlledRunner = ControlledRunner<List<ProductListing>>()
suspendfunfetchProductsFromBackend(): List<ProductListing> { // if there's already a request running, return the result from the // existing request. If not, start a new request by running the block. return controlledRunner.joinPreviousOrRun { val result = productsApi.getProducts() productsDao.insertAll(result) result } } }
This inverts the behavior of cancelPreviousAndRun. Instead of discarding the previous request by cancelling it — it will discard the new request and avoid running it. If there’s already a request running, it waits for the result of current “in flight” request and returns that instead of running a new one. The block will only be executed if there was not already a request running.
You can see how this works at the start of joinPreviousOrRun — it just returns the previous result if there’s anything in activeTask:
1 2 3 4 5 6 7 8 9
// see the complete implementation at // https://gist.github.com/objcode/7ab4e7b1df8acd88696cb0ccecad16f7#file-concurrencyhelpers-kt-L124
suspendfunjoinPreviousOrRun(block: suspend () -> T): T { // if there is an activeTask, return it's result and don't run the block activeTask?.let { return it.await() } // ...
This pattern scales well for requests like fetching products by id. You could add add a map from id to Deferred then use the same join logic to keep track of previous requests for the same product.
Join previous work is a great solution to avoiding repeated network requests.
What’s next?
In this post we explored how to implement a one shot request using Kotlin coroutines. To start out we implemented a complete pattern showing how to start a coroutine in the ViewModel and then expose regular suspend functions from a Repository and Room Dao.
For most tasks, this is all you need to do in order to use Kotlin coroutines on Android. This pattern can be applied to many common tasks like sorting a list like we showed here. You can also use it to fetch, save, or update data on the network
We then looked at a subtle bug that can come up and possible solutions. The easiest (and often best) way to fix this is in the UI — just disable the sort buttons while a sort is in progress.
And wrapping up we looked at some advanced concurrency patterns and how to implement them in Kotlin coroutines. The code for this is a bit complex, but it does provide a good introduction to some advanced coroutines topics.
In the next post, we’ll take a look at streaming requests and explore how to use the liveData builder!