Kotlin android app sample downloads






















During the codelab, you'll be presented with snippets of code that you'll have to add to the project. In some places, you'll also have to remove code that will be explicitly mentioned and in comments on the code snippets. As checkpoints, you have the intermediate branches available in case you need help with a particular step.

Download the final code. First, let's see what the starting sample app looks like. Follow these instructions to open the sample app in Android Studio. Take a moment to familiarize yourself with the structure of the project. The code in the main branch manages dependencies manually. Instead of creating them by hand, we will refactor the app to use Dagger to manage them for us.

This codelab is not opinionated in the way you architect your app. It's intended to showcase different ways you could plug Dagger into your app architecture: single Activity with multiple fragments registration and login flows or multiple Activities main app flow. Complete the codelab to understand the main concepts of Dagger so you can apply them to your project accordingly.

Some patterns used in this codelab are not the recommended way to build Android applications, however, they're the best ones to explain Dagger. To learn more about Android app architecture, visit our Guide to App architecture page. If the application gets larger, we will start writing a lot of boilerplate code e.

Doing this wrong can lead to subtle bugs and memory leaks in your app. In the codelab, we will see how to use Dagger to automate this process and generate the same code you would have written by hand otherwise. Dagger will be in charge of creating the application graph for us.

We'll also use Dagger to perform field injection in our Activities instead of creating the dependencies by hand. More information about Why Dagger here. After adding these lines to the file, click on the "Sync Now" button that appears at the top of the file.

That will sync the project and download the new dependencies. We're now ready to use Dagger in the app. Dagger is implemented using Java's annotations model.

It generates code at compile-time using an annotation processor. Annotation processors are supported in Kotlin with the kapt compiler plugin. They are enabled by adding id 'kotlin-kapt' to the top of the file below the id 'kotlin-android-extensions' line.

In the dependencies, the dagger library contains all the annotations you can use in your app and dagger-compiler is the annotation processor that will generate the code for us.

The latter will not be packed into your app. You can find the latest available versions of Dagger here. In order to build the application graph automatically for us, Dagger needs to know how to create instances for the classes in the graph. One way to do this is by annotating the constructor of classes with Inject. The constructor parameters will be the dependencies of that type. Open the RegistrationViewModel. In Kotlin, to apply an annotation to the constructor, you need to specifically add the keyword constructor and introduce the annotation just before it as shown in the code snippet above.

Dagger doesn't know how to create types of UserManager yet. Follow the same process, and add the Inject annotation to UserManager 's constructor. Open the UserManager. Since UserManager 's dependency i. Storage is an interface, we need to tell Dagger how to create an instance of that in a different way, we'll cover that later. Certain Android framework classes such as Activities and Fragments are instantiated by the system so Dagger can't create them for you.

For Activities specifically, any initialization code needs to go to the onCreate method. Because of that, we cannot use the Inject annotation in the constructor of a View class as we did before that is what is called constructor injection. Instead, we have to use field injection. Instead of creating the dependencies an Activity requires in the onCreate method as we do with manual dependency injection, we want Dagger to populate those dependencies for us.

For field injection that is commonly used in Activities and Fragments , we annotate with Inject the fields that we want Dagger to provide. If you open RegistrationActivity. We don't want to create it by hand, we want Dagger to provide it. For that, we need to:. How can we tell Dagger which objects need to be injected into RegistrationActivity?

We need to create the Dagger graph or application graph and use it to inject objects into the Activity. We want Dagger to create the graph of dependencies of our project, manage them for us and be able to get dependencies from the graph. To make Dagger do it, we need to create an interface and annotate it with Component. Dagger will create a Container as we would have done with manual dependency injection. An interface annotated with Component will make Dagger generate code with all the dependencies required to satisfy the parameters of the methods it exposes.

Inside that interface, we can tell Dagger that RegistrationActivity requests injection. Create a new package called di under com.

Inside that package, create a new Kotlin file called AppComponent. With the inject activity: RegistrationActivity method in the Component interface, we're telling Dagger that RegistrationActivity requests injection and that it has to provide the dependencies which are annotated with Inject i. RegistrationViewModel as we defined in the previous step. Since Dagger has to create an instance of RegistrationViewModel , internally, it also needs to satisfy RegistrationViewModel 's dependencies i.

If during this recursive process of finding dependencies Dagger doesn't know how to provide a particular dependency, it will fail at compile time saying there's a dependency that it cannot satisfy. Building the app triggers Dagger's annotation processor that will generate the code we need for managing our dependencies. If we do it by using the build button in Android Studio, we get the following error you might need to enable soft-wrap using this button to see the error easily :.

Let's break this error message down. First, it's telling us we're getting an error in AppComponent. If we keep reading, it says that Storage cannot be provided without an Provides -annotated method.

We haven't told Dagger how to provide an object of type Storage which is needed by UserManager! The way we tell Dagger how to provide Storage is different because Storage is an interface and as such cannot be instantiated directly. We need to tell Dagger what implementation of Storage we want to use. In this case it's SharedPreferencesStorage.

To do this we will use a Dagger Module. A Dagger Module is a class that is annotated with Module. Similar to Components, Dagger Modules tell Dagger how to provide instances of a certain type.

Dependencies are defined using the Provides and Binds annotations. Since this Module will contain information about storage, let's create another file called StorageModule. In that file, we define a class called StorageModule and annotate it with Module.

Use Binds to tell Dagger which implementation it needs to use when providing an interface. Binds must annotate an abstract function. The return type of the abstract function is the interface we want to provide an implementation for i.

The implementation is specified by adding a parameter with the interface implementation type i. We've told Dagger that when a Storage object is requested it should create an instance of SharedPreferencesStorage , but we haven't yet told Dagger how. We do that the same way as before, by annotating the constructor of SharedPreferencesStorage with Inject. The application graph needs to know about StorageModule. For that, we include it in AppComponent with the modules parameter inside the Component annotation as follows:.

In this way, AppComponent can access the information that StorageModule contains. In a more complex application, we could also have a NetworkModule that adds information on how to provide an OkHttpClient , or how to configure Gson or Moshi , for example. If we try to build again we get an error very similar to what we got before!

This time, what Dagger doesn't find is: Context. How can we tell Dagger how to provide Context? Context is provided by the Android system and therefore constructed outside of the graph. Since Context is already available at the time we'll be creating an instance of the graph, we can pass it in.

The way to pass it in is with a Component Factory and using the BindsInstance annotation. We're declaring an interface annotated with Component. Inside, there's a method that returns the component type i.

AppComponent and has a parameter of type Context annotated with BindsInstance. BindsInstance tells Dagger that it needs to add that instance in the graph and whenever Context is required, provide that instance. Your project should now build with no errors.. Dagger has generated the application graph successfully and you're ready to use it.

The implementation of the application graph is automatically generated by the annotation processor. We'll use the generated DaggerAppComponent class in the next section.

AppComponent includes StorageModule with information on how to provide Storage instances. Storage has a dependency on Context but since we're providing it when we create the graph, Storage has all its dependencies covered. The instance of Context is passed in the AppComponent 's factory create method. Therefore, we'll have the same instance provided anytime an object needs Context. That's represented with a white dot in the diagram.

Now, RegistrationActivity can access the graph to get objects injected or populated by Dagger, in this case RegistrationViewModel because it is a field which is annotated with Inject. To do this it needs to satisfy RegistrationViewModel 's dependencies i. UserManager and create an instance of UserManager too.

As UserManager has its constructor annotated with Inject , Dagger will use it to create instances. UserManager has a dependency on Storage but since it's already in the graph, nothing else is needed. In Android, you usually create a Dagger graph that lives in your Application class because you want the graph to be in memory as long as the app is running.

In this way, the graph is attached to the app's lifecycle. In our case, we also want to have the application Context available in the graph. As advantages, the graph is available to other Android framework classes that can access with their Context and it's also good for testing since you can use a custom Application class in tests.

Let's add an instance of the graph i. AppComponent to our custom Application: MyApplication. As we mentioned in the previous section, Dagger generated a class called DaggerAppComponent containing the implementation of the AppComponent graph when we built the project. Since we defined a Component Factory with the Component.

Factory annotation, we can call. With that, we can now call the create method we defined inside the factory where we pass in the Context , in this case applicationContext. We do that using a Kotlin lazy initialization so that the variable is immutable and it's only initialized when needed. We can use this instance of the graph in RegistrationActivity to make Dagger inject the fields annotated with Inject. How can we do it?

We have to call the AppComponent 's inject method that takes RegistrationActivity as a parameter. Calling appComponent. RegistrationActivity is already using Dagger to manage its dependencies!

You can go ahead and run the app. Did you find a bug? The Main page should appear after the Registration flow! But it doesn't and Login does. The other flows of the app are not using the Dagger graph yet. MainActivity is still using the userManager defined in MyApplication whereas Registration has used a userManager instance from the Dagger graph. As before, we want MainActivity to use Dagger to manage its dependencies.

To tell Dagger that MainActivity requests injection, we have to add another function with MainActivity as a parameter to the AppComponent 's interface. The name of the functions don't matter that's why we called both of them inject , what matters is the parameter type. Let's define what we want to inject from Dagger in the MainActivity and inject the graph:. UserManager is already available in the graph so Dagger knows how to provide it but MainViewModel is not.

Let's add the Inject annotation to its constructor so that Dagger knows how to create instances of the class. Since UserManager is already part of the graph, Dagger has all the information it needs to build the graph successfully. What does it say? That's one of the disadvantages of Dagger, injected fields need to have at least visibility package-private or higher.

They cannot be private to their own class. The project can be successfully built now. Let's run the app again. When you run again, since we registered a user before, you'll be presented with the Login screen. To start fresh as we had run the application for the first time, click on "Unregister" to go to the Registration flow. When you register, it doesn't go to the Main page! It goes to the Login Activity again. The bug is happening again, but why?

Both main and registration flow are getting UserManager injected from the application graph. The problem is that Dagger always provides a new instance of a type in our case UserManager when injecting dependencies by default. How can we make Dagger to reuse the same instance every time? With Scoping.

Sometimes, you might want to provide the same instance of a dependency in a Component for multiple reasons:. Use Scopes to have a unique instance of a type in a Component. This is what is also called " to scope a type to the Component's lifecycle ". Scoping a type to a Component means that the same instance of that type will be used every time the type needs to be provided. For AppComponent , we can use the Singleton scope annotation that is the only scope annotation that comes with the javax.

If we annotate a Component with Singleton , all the classes also annotated with Singleton will be scoped to its lifetime. Open the AppComponent. Now, classes annotated with Singleton will be scoped to AppComponent. Let's annotate UserManager to have a unique instance of it in the application graph.

Run the app again and go to the Registration flow to start fresh as we did before. Now, when you complete registration, it goes to the Main flow!

Problem solved. Notice that UserManager is also marked with the white dot. As it happened with Context , the same instance of UserManager will be provided when required as dependency in the same instance of AppComponent. Let's keep refactoring our app to Dagger. Registration Fragments are still using manual dependency injection, let's migrate those now.

Which fields do we want Dagger to provide? We do that by annotating the fields with Inject and removing the private visibility modifier. But we also have to remove the manual instantiations we have in the code.

Remove the following lines:. Now, we can use the appComponent instance in the Application class to inject the Fragments. For Fragments, we inject Components using the onAttach method after calling super. We do that by annotating its constructor with Inject. Now you can run the app. What happened? It crashed after registering! However, that's not what we want. We want the same instance to be injected for the Activity and Fragments. What if we annotate RegistrationViewModel with Singleton? That would solve the problem for now but it will create problems in the future:.

We want the registration Fragments to reuse the same ViewModel coming from the Activity, but if the Activity changes, we want a different instance. We need to scope RegistrationViewModel to RegistrationActivity , for that, we can create a new Component for the registration flow and scope the ViewModel to that new registration Component.

To achieve this we use Dagger subcomponents. The way to tell Dagger that we want a new Component to use part of another Component is with Dagger Subcomponents. The new component i. RegistrationComponent must be a subcomponent of the one containing shared resources i. Since this is specific to registration, create a new file named RegistrationComponent.

Here you can create a new interface called RegistrationComponent annotated with Subcomponent that tells Dagger that this is a Subcomponent. In AppComponent , we have to remove the methods that can inject registration view classes because these won't be used anymore, those classes will use the RegistrationComponent. Instead, for the RegistrationActivity to create instances of RegistrationComponent , we need to expose its Factory out in the AppComponent interface.

We expose the RegistrationComponent factory by declaring a function with that class as return type. Now, we have to make AppComponent know that RegistrationComponent is its subcomponent so that it can generate code for that. We need to create a Dagger module to do this. Let's create a file called AppSubcomponents. In that file, we define a class called AppSubcomponents annotated with Module. To specify information about subcomponents, we add a list of component class names to the subcomponents variable in the annotation as follows:.

This new module also needs to be included in the AppComponent :. AppComponent is now aware that RegistrationComponent is its subcomponent. View classes specific to registration get injected by the RegistrationComponent.

We created a subcomponent because we needed to share the same instance of RegistrationViewModel between the Activity and Fragments. As we did before, if we annotate the Component and classes with the same scope annotation, that'll make that typehave a unique instance in the Component.

However, we cannot use Singleton because it's already been used by AppComponent. We need to create a different one. In this case, we could call this scope RegistrationScope but this is not a good practice. The scope annotation's name should not be explicit to the purpose it fulfills. It should be named depending on the lifetime it has since annotations can be reused by sibling Components e.

LoginComponent , SettingsComponent , etc. That's why instead of calling it RegistrationScope , we call it ActivityScope. Let's create a file called ActivityScope. AppComponent is attached to the lifecycle of the Application because we want to use the same instance of the graph as long as the application is in memory. What's the lifecycle of RegistrationComponent? One of the reasons why we needed it is because we wanted to share the same instance of the RegistrationViewModel between the registration Activity and Fragments.

But we also want a new instance of RegistrationViewModel whenever there's a new registration flow. RegistrationActivity is the right lifetime for RegistrationComponent : for every new Activity, we'll create a new RegistrationComponent and Fragments that can use that instance of RegistrationComponent.

Since RegistrationComponent is attached to the RegistrationActivity lifecycle, we have to keep a reference to the component in the Activity in the same way we kept the reference to the appComponent in the Application class.

In this way, Fragments will be able to access it. Create a new instance of RegistrationComponent in the onCreate method before calling super. Replace the onAttach method in the Fragments to use the registrationComponent from the Activity:. If you run the app again and go to the Registration flow to start fresh as we did before, you can see that the registration flow works as expected.

Note that Settings will cause the app to crash because it hasn't been refactored to use Dagger. We'll get to that later.

That means it won't make extra network requests or database queries. The next few lines of code actually call refreshTitle in the repository. Before this coroutine does anything it starts the loading spinner — then it calls refreshTitle just like a regular function.

However, since refreshTitle is a suspending function, it executes differently than a normal function. We don't have to pass a callback. The coroutine will suspend until it is resumed by refreshTitle.

While it looks just like a regular blocking function call, it will automatically wait until the network and database query are complete before resuming without blocking the main thread. Exceptions in suspend functions work just like errors in regular functions. If you throw an error in a suspend function, it will be thrown to the caller. This is useful because it lets you rely on the built-in language support for error handling instead of building custom error handling for every callback.

And, if you throw an exception out of a coroutine — that coroutine will cancel its parent by default. That means it's easy to cancel several related tasks together.

And then, in a finally block, we can make sure that the spinner is always turned off after the query runs. Run the application again by selecting the start configuration then pressing , you should see a loading spinner when you tap anywhere. The title will stay the same because we haven't hooked up our network or database yet.

In this exercise you'll learn how to switch the thread a coroutine runs on in order to implement a working version of TitleRepository. Open TitleRepository. In TitleRepository. This callback based implementation is main-safe because it won't block the main thread. But, it has to use a callback to inform the caller when the work completes. Without introducing coroutines to the network or database, we can make this code main-safe using coroutines.

This will let us get rid of the callback and allow us to pass the result back to the thread that initially called it. You can use this pattern anytime you need to do blocking or CPU intensive work from inside a coroutine such as sorting and filtering a large list or reading from disk. To switch between any dispatcher, coroutines uses withContext.

Calling withContext switches to the other dispatcher just for the lambda then comes back to the dispatcher that called it with the result of that lambda. This implementation uses blocking calls for the network and database — but it's still a bit simpler than the callback version.

This code still uses blocking calls. Calling execute and insertTitle However, by switching to Dispatchers. IO using withContext , we're blocking one of the threads in the IO dispatcher.

The coroutine that called this, possibly running on Dispatchers. Main , will be suspended until the withContext lambda is complete. If you run the app again, you'll see that the new coroutines-based implementation is loading results from the network! To continue the coroutines integration, we're going to use the support for suspend functions in the stable version of Room and Retrofit, then simplify the code we just wrote substantially by using the suspend functions.

First open MainDatabase. When you do this, Room will make your query main-safe and execute it on a background thread automatically. However, it also means that you can only call this query from inside a coroutine. Next let's see how to integrate coroutines with Retrofit. Open up MainNetwork. Retrofit will automatically make suspend functions main-safe so you can call them directly from Dispatchers.

Now that Room and Retrofit support suspend functions, we can use them from our repository. Open up TitleRepository. Wow, that's a lot shorter. What happened? It turns out relying on suspend and resume lets code be much shorter. Retrofit lets us use return types like String or a User object here, instead of a Call.

That's safe to do, because inside the suspend function, Retrofit is able to run the network request on a background thread and resume the coroutine when the call completes. Even better, we got rid of the withContext. Since both Room and Retrofit provide main-safe suspending functions, it's safe to orchestrate this async work from Dispatchers. Moving to coroutines does involve changing the signature of functions as you can't call a suspend function from a regular function.

When you added the suspend modifier in this step, a few compiler errors were generated that show what would happen if you changed a function to suspend in a real project.

Go through the project and fix the compiler errors by changing the function to suspend created. Here are the quick resolutions for each:. Run the app again, once it compiles, you will see that it's loading data using coroutines all the way from the ViewModel to Room and Retrofit!

Congratulations, you've completely swapped this app to using coroutines! To wrap up we'll talk a bit about how to test what we just did.

In this exercise, you'll write a test that calls a suspend function directly. Since refreshTitle is exposed as a public API it will be tested directly, showing how to call coroutines functions from tests. Here's the refreshTitle function you implemented in the last exercise:.

Open TitleRepositoryTest. Since refreshTitle is a suspend function Kotlin doesn't know how to call it except from a coroutine or another suspend function, and you will get a compiler error like, "Suspend function refreshTitle should be called only from a coroutine or another suspend function. The test runner doesn't know anything about coroutines so we can't make this test a suspend function. We could launch a coroutine using a CoroutineScope like in a ViewModel , however tests need to run coroutines to completion before they return.

Once a test function returns, the test is over. Coroutines started with launch are asynchronous code, which may complete at some point in the future. Therefore to test that asynchronous code, you need some way to tell the test to wait until your coroutine completes.

Since launch is a non-blocking call, that means it returns right away and can continue to run a coroutine after the function returns - it can't be used in tests. For example:. This test will sometimes fail. The call to launch will return immediately and execute at the same time as the rest of the test case.

The test has no way to know if refreshTitle has run yet or not — and any assertions like checking that the database was updated would be flakey. And, if refreshTitle threw an exception, it will not be thrown in the test call stack. It will instead be thrown into GlobalScope 's uncaught exception handler.

The library kotlinx-coroutines-test has the runBlockingTest function that blocks while it calls suspend functions. When runBlockingTest calls a suspend function or launches a new coroutine, it executes it immediately by default. You can think of it as a way to convert suspend functions and coroutines into normal function calls.

In addition, runBlockingTest will rethrow uncaught exceptions for you. This makes it easier to test when a coroutine is throwing an exception. This test uses the fakes provided to check that "OK" is inserted to the database by refreshTitle. When the test calls runBlockingTest , it will block until the coroutine started by runBlockingTest completes. Then inside, when we call refreshTitle it uses the regular suspend and resume mechanism to wait for the database row to be added to our fake.

After the test coroutine completes, runBlockingTest returns. We want to add a short timeout to the network request. Let's write the test first then implement the timeout.

Create a new test:. This test uses the provided fake MainNetworkCompletableFake , which is a network fake that's designed to suspend callers until the test continues them. When refreshTitle tries to make a network request, it'll hang forever because we want to test timeouts.

Then, it launches a separate coroutine to call refreshTitle. This is a key part of testing timeouts, the timeout should happen in a different coroutine than the one runBlockingTest creates.

One of the features of runBlockingTest is that it won't let you leak coroutines after the test completes. If there are any unfinished coroutines, like our launch coroutine, at the end of the test, it will fail the test.

Open up TitleRepository and add a five second timeout to the network fetch. You can do this by using the withTimeout function:. In this exercise you'll refactor refreshTitle in MainViewModel to use a general data loading function. This will teach you how to build higher order functions that use coroutines. The current implementation of refreshTitle works, but we can create a general data loading coroutine that always shows the spinner. This might be helpful in a codebase that loads data in response to several events, and wants to ensure the loading spinner is consistently displayed.

Reviewing the current implementation every line except repository. Now refactor refreshTitle to use this higher order function. By abstracting the logic around showing a loading spinner and showing errors, we've simplified our actual code needed to load data. Showing a spinner or displaying an error is something that's easy to generalize to any data loading, while the actual data source and destination needs to be specified every time.

To build this abstraction, launchDataLoad takes an argument block that is a suspend lambda. A suspend lambda allows you to call suspend functions. That's how Kotlin implements the coroutine builders launch and runBlocking we've been using in this codelab. To make a suspend lambda, start with the suspend keyword. The function arrow and return type Unit complete the declaration. You don't often have to declare your own suspend lambdas, but they can be helpful to create abstractions like this that encapsulate repeated logic!

There are many options on Android for deferrable background work. This exercise shows you how to integrate WorkManager with coroutines. WorkManager is a compatible, flexible and simple library for deferrable background work. WorkManager is the recommended solution for these use cases on Android. WorkManager is part of Android Jetpack , and an Architecture Component for background work that needs a combination of opportunistic and guaranteed execution.

Opportunistic execution means that WorkManager will do your background work as soon as it can. Guaranteed execution means that WorkManager will take care of the logic to start your work under a variety of situations, even if you navigate away from your app. WorkManager provides different implementations of its base ListenableWorker class for different use cases. The simplest Worker class allows us to have some synchronous operation executed by WorkManager. However, having worked so far to convert our codebase to use coroutines and suspend functions, the best way to use WorkManager is through the CoroutineWorker class that allows to define our doWork function as a suspend function.

To get started, open up RefreshMainDataWork. It already extends CoroutineWorker , and you need to implement doWork.

Inside the suspend doWork function, call refreshTitle from the repository and return the appropriate result! Note that CoroutineWorker. You can switch to other dispatchers by using withContext. WorkManager makes available a couple of different ways to test your Worker classes, to learn more about the original testing infrastructure, you can read the documentation. WorkManager v2. Before we get to the test, we tell WorkManager about the factory so we can inject the fake network.

The test itself uses the TestListenableWorkerBuilder to create our worker that we can then run calling the startWork method. In this codelab we have covered the basics you'll need to start using coroutines in your app! For testing coroutine based code, we covered both by testing behavior as well as directly calling suspend functions from tests.

To learn more about cancellation and exceptions in coroutines, check out this article series: Part 1: Coroutines , Part 2: Cancellation in coroutines , and Part 3: Exceptions in coroutines. Kotlin coroutines have many features that weren't covered by this codelab. If you're interested in learning more about Kotlin coroutines, read the coroutines guides published by JetBrains. Also check out " Improve app performance with Kotlin coroutines " for more usage patterns of coroutines on Android.

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4. For details, see the Google Developers Site Policies. Before you begin In this codelab you'll learn how to use Kotlin Coroutines in an Android app—the recommended way of managing background threads that can simplify code by reducing the need for callbacks. Experience with Kotlin syntax, including extension functions and lambdas.

A basic understanding of using threads on Android, including the main thread, background threads, and callbacks. What you'll do Call code written with coroutines and obtain results. Use suspend functions to make async code sequential. Use launch and runBlocking to control how code executes. Learn techniques to convert existing APIs to coroutines using suspendCoroutine. Use coroutines with Architecture Components.

Learn best practices for testing coroutines. What you'll need Android Studio 4. Getting set up Download the code Click the following link to download all the code for this codelab: Download Zip How do I set up a device for development? Run the starting sample app First, let's see what the starting sample app looks like. If you downloaded the kotlin-coroutines zip file, unzip the file. Open the coroutines-codelab project in Android Studio. Select the start application module.

Click the Run button, and either choose an emulator or connect your Android device, which must be capable of running Android Lollipop the minimum SDK supported is The Kotlin Coroutines screen should appear: This starter app uses threads to increment the count a short delay after you press the screen.

MainActivity displays the UI, registers click listeners, and can display a Snackbar. TitleRepository fetches results from the network and saves them to the database. Adding coroutines to a project To use coroutines in Kotlin, you must include the coroutines-core library in the build. Coroutines on Android are available as a core library, and Android specific extensions: kotlinx-coroutines-core — Main interface for using coroutines in Kotlin kotlinx-coroutines-android — Support for the Android Main thread in coroutines The starter app already includes the dependencies in build.

Coroutines in Kotlin On Android, it's essential to avoid blocking the main thread. The callback pattern One pattern for performing long-running tasks without blocking the main thread is callbacks. Take a look at an example of the callback pattern. Using coroutines to remove callbacks Callbacks are a great pattern, however they have a few drawbacks. Controlling the UI with coroutines In this exercise you will write a coroutine to display a message after a delay.

Switch from threads to coroutines In MainViewModel. However, there are some important differences: viewModelScope. If the user left the Activity before delay returned, this coroutine will automatically be cancelled when onCleared is called upon destruction of the ViewModel. Since viewModelScope has a default dispatcher of Dispatchers. Main , this coroutine will be launched in the main thread. We'll see later how to use different threads. The function delay is a suspend function.

This is shown in Android Studio by the icon in the left gutter. Even though this coroutine runs on the main thread, delay won't block the thread for one second. Instead, the dispatcher will schedule the coroutine to resume in one second at the next statement. Go ahead and run it. When you click on the main view you should see a snackbar one second later. In the next section we'll consider how to test this function.

Testing coroutines through behavior In this exercise you'll write a test for the code you just wrote. Main to use a TestCoroutineDispatcher from kotlinx-coroutines-test. This allows tests to advance a virtual-clock for testing, and allows code to use Dispatchers. Main in unit tests. Write a test that controls coroutines Add a new test that ensures that taps are updated one second after the main view is clicked: MainViewModelTest.

Run the existing test Right click on the class name MainViewModelTest in your editor to open a context menu. By default, the configuration will be called MainViewModelTest.



0コメント

  • 1000 / 1000