Skip to main content

5 posts tagged with "kotlin"

View All Tags

· 11 min read
Petros Efthymiou

From Android Views to Jetpack Compose

Jetpack Compose and why it matters

Jetpack Compose is a revolutionary UI toolkit introduced by Google for building native Android applications. Unlike traditional Android Views, Jetpack Compose adopts a declarative approach to UI development, allowing developers to create user interfaces using composable functions.

This paradigm shift simplifies UI development by eliminating the need for complex view hierarchies and manual view updates. With Jetpack Compose, developers can express the desired UI state and let the framework handle the rendering and updating automatically. This results in cleaner and more readable code, improved productivity, and faster UI development cycles.

Jetpack Compose offers a modern and intuitive way to build UIs, enabling developers to create beautiful, responsive, and highly interactive Android applications with ease. Its importance lies in providing a more efficient and enjoyable development experience, enabling developers to focus on crafting exceptional user experiences while reducing boilerplate code and increasing code maintainability.

And the cherry on top? No more Android Fragments! We all had our fair share of pain trying to comprehend and debug the complex Fragment lifecycle. With Jetpack Compose, we can put an end to it! That’s right, Composables can take the Fragments’ place as reusable UI components that are tied up to an Activity.

Declarative UI building is the way that all front-facing applications are moving towards. It was first introduced by React in 2013. After its successful adoption in the web, it later moved to cross platform mobile platforms such as React Native and Flutter. Realizing its advantages, both native platforms, Android and iOS, have recently made a similar move by introducing Jetpack Compose and SwiftUI. Soon all other UI-creating tools will be a thing of the past.

Understanding RecyclerView and its Limitations

RecyclerView has long been a popular component in Android app development for efficiently displaying lists and grids. It offers flexibility and performance optimizations by recycling views as users scroll through the list, reducing memory consumption and improving scrolling smoothness. However, RecyclerView also comes with its limitations. Managing view recycling, implementing complex adapter logic, and supporting different view types for diverse list items can often lead to boilerplate code and increased development effort.

Additionally, RecyclerView lacks built-in support for animations and complex layout transitions, making it challenging to create dynamic and visually engaging user interfaces. These limitations have prompted developers to seek alternative solutions that offer a more streamlined and intuitive approach to building user interfaces. The Jetpack Compose Column and Lazy Column are coming to the rescue.

Analyzing the Existing RecyclerView Implementation

We are creating an application that fetches a list of playlists and displays them on the screen. The initial implementation is based on Android Fragment and Recycler View. Let's take a closer look at the code structure and components involved:

class PlaylistFragment : Fragment() {

private val viewModel: PlaylistViewModel by viewModels()
@Injected
var playlistAdapter: PlaylistAdapter

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_playlist, container, false)

val playlistsRecyclerView: RecyclerView = view.findViewById(R.id.recyclerView)
playlistsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
playlistsRecyclerView.adapter = playlistAdapter

lifecycleScope.launchWhenStarted {
viewModel.playlists.collect { playlists ->
playlistAdapter.submitList(playlists)
}
}

return view
}
}

Our Fragment depends on the ViewModel, which exposes a Kotlin StateFlow that emits a list of playlists. We observe this StateFlow using the collect method, and upon receiving the updated list, we populate the RecyclerView with the playlist items by calling submitList. The RecyclerView is set up with a custom adapter that extends the RecyclerView Adapter and holds a list of playlists as its data source.

Below is the respective code for the RecyclerView Adapter:

class PlaylistAdapter : RecyclerView.Adapter<PlaylistAdapter.PlaylistViewHolder>() {

private var playlistItems: List<Playlist> = emptyList()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaylistViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.item_playlist, parent, false)
return PlaylistViewHolder(itemView)
}

override fun onBindViewHolder(holder: PlaylistViewHolder, position: Int) {
val playlist = playlistItems[position]
holder.bind(playlist)
}

override fun getItemCount(): Int {
return playlistItems.size
}

inner class PlaylistViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val titleTextView: TextView = itemView.findViewById(R.id.titleTextView)
private val descriptionTextView: TextView = itemView.findViewById(R.id.descriptionTextView)

fun bind(playlist: Playlist) {
titleTextView.text = playlist.title
descriptionTextView.text = playlist.description
}
}

fun submitList(playlists: List<Playlist>) {
playlistItems = playlists
notifyDataSetChanged()
}
}

Within the adapter, we override the necessary methods, such as onCreateViewHolder, onBindViewHolder, and getItemCount to handle view creation, data binding, and determining the item count respectively. The item layout XML file defines the visual representation of each playlist item, containing the necessary views and bindings.

As we explained earlier, RecyclerView implementations require a lot of boilerplate and repetitive code.

Jetpack Compose Column vs Lazy Column

Before we jump into improving our implementation with Jetpack Compose, let’s discuss the differences between the Column and LazyColumn components.

In Jetpack Compose, both Column and LazyColumn are composable functions used to display vertical lists of UI elements. The primary difference lies in their behavior and performance optimization. The Column is suitable for a small number of items or when the entire list can fit on the screen. It lays out all its children regardless of whether they are currently visible on the screen, which may lead to performance issues with large lists. For short lists, rendering the items from the start offers increased performance.

On the other hand, LazyColumn is optimized for handling large lists efficiently. It loads only the visible items on the screen and recycles the off-screen items, similar to the traditional RecyclerView. This approach reduces memory consumption and enhances scrolling performance for long lists. Therefore, LazyColumn is the preferred choice when dealing with extensive datasets or dynamic content, ensuring a smooth and responsive user experience.

Setting Up Jetpack Compose in the Project

In order to use Jetpack Compose in our project, we need to complete the following setup steps:

Step 1: Add the Jetpack Compose dependency in build.gradle

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

android {
// ...
buildFeatures {
compose true // Enable Jetpack Compose
}

composeOptions {
kotlinCompilerExtensionVersion = “$version”
}
// ...
}

dependencies {
implementation "androidx.compose.ui:ui:$compose_version" // Check for the latest version
implementation "androidx.compose.material:material:$material_version" // Check for the latest version
implementation "androidx.activity:activity-compose:$compose_version" // Check for the latest version
// ...
}

Step 2: Initialize Jetpack Compose In your Application class, in the onCreate method.

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) // Optional: Disable dark mode
}
}

You can now start adding Composables inside your MainActivity and leverage the power of Jetpack Compose!

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
//TODO add a composable
}
}
}

Migrating RecyclerView to Lazy Column

Jetpack Compose belongs to the declarative UI family. In declarative UI, we receive the state of the data that needs to be displayed, and we programmatically create the views. The views are immutable, and their state cannot change. Every time the data state changes, everything is being redrawn on the screen, and the views recreated from scratch. Practically, behind the scenes, there are smart diffing mechanisms that don’t redraw elements that their data hasn’t changed. But we, as developers, must write code as if everything is being redrawn when the data changes.

Let’s see how we can refactor the playlists screen with Jetpack Compose.

As we promised earlier, with Jetpack Compose, we can get rid of the Android Fragments. Everything is Jetpack Compose, from a whole screen to a small UI element, is a composable. The composables are functions instead on objects. This reflects one of the paradigm shifts that the declarative UI introduces. We are moving towards stateless functional programming instead of stafeful object oriented programming.

Let’s start by replacing our PlaylistFragment with a screen composable.

@Composable
fun PlaylistScreen(viewModel: PlaylistViewModel) {
val playlists by viewModel.playlists.collectAsState()

LazyColumn {
items(playlists) { playlist ->
PlaylistItem(playlist = playlist)
}
}
}

The PlaylistScreen composable represents the screen where the playlists are displayed. It collects the playlists from the PlaylistViewModel using collectAsState to recompose the composable whenever the playlist data changes automatically. The main component in the PlaylistScreen is the LazyColumn, which is a Jetpack Compose equivalent of RecyclerView. It handles view recycling and renders only the visible items on the screen. Every time the playlist StateFlow emits another result, the composable function PlaylistScreen will automatically recompose, and the UI be redrawn with the updated data.

Each list item is described by the composable below:

@Composable
fun PlaylistItem(playlist: Playlist) {
// Custom composable for rendering an individual playlist item
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = playlist.title,
style = TextStyle(fontWeight = FontWeight.Bold, fontSize = 18.sp)
)
Spacer(modifier = Modifier.height(8.dp))
Text(text = playlist.description)
}
}

The PlaylistItem composable represents an individual playlist item. We use a Column composable to stack the title and description texts vertically. We apply styling and padding.

With Jetpack Compose's LazyColumn, we achieve a more concise and declarative way of displaying the list of playlists without the need for a separate adapter or view holder logic. The composable functions automatically handle the UI rendering and updates based on the provided state. This refactoring results in cleaner, moer reuseable and more maintainable code, making UI development more intuitive and efficient. Furthermore, we don’t have to handle the Fragment’s complex lifecycle while retaining the benefit of reusable UI components.

The playlist with Compose&#39;s LazyColumn

Figure: The playlist with Compose's LazyColumn

Handling Clicks

Handling clicks in the Jetpack Compose Column component is super easy, we simply need to add the ‘clickable’ modifier and call the code that we want to execute when the respective list item is clicked. We have access to selected playlist model info.

 @Composable
fun PlaylistItem(playlist: Playlist) {
// Custom composable for rendering an individual playlist item
Column(
modifier = Modifier
.fillMaxWidth()
.clickable { /* Handle item click here */ }
.padding(16.dp)
) {
Text(
text = playlist.title,
style = TextStyle(fontWeight = FontWeight.Bold, fontSize = 18.sp)
)
Spacer(modifier = Modifier.height(8.dp))
Text(text = playlist.description)
}
}

Testing

As good engineers, we should always include automated tests that verify that our code works correctly. With Jetpack Compose, UI testing is much easier than before. Let’s see how we can test the PlaylistScreen after we migrate it to Jetpack Compose.

@ExperimentalCoroutinesApi
@get:Rule
val composeTestRule = createComposeRule()

@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun playlistScreen_RenderList_Success() {
// Dummy data for testing
val playlists = listOf(
Playlist("Playlist 1", "Description 1"),
Playlist("Playlist 2", "Description 2"),
Playlist("Playlist 3", "Description 3")
)

// Create a TestCoroutineDispatcher to be used with Dispatchers.Main
val testDispatcher = TestCoroutineDispatcher()
val testCoroutineScope = TestCoroutineScope(testDispatcher)

// Launch the composable with TestCoroutineScope
testCoroutineScope.launch {
composeTestRule.setContent {
PlaylistScreen(viewModel = PlaylistViewModel(playlists))
}
}

// Wait for recomposition
composeTestRule.waitForIdle()

// Check if each playlist item is rendered correctly
playlists.forEach { playlist ->
composeTestRule.onNode(hasText(playlist.title)).assertIsDisplayed()
composeTestRule.onNode(hasText(playlist.description)).assertIsDisplayed()
}
}

In this test, we use the createComposeRule to set up the Compose test rule. We also create a TestCoroutineDispatcher and a TestCoroutineScope to simulate the background coroutine execution. Then, we launch the PlaylistScreen composable with dummy data for testing. After the recomposition, we use onNode to check if each playlist item title and description is correctly displayed. Note that we are testing UI, therefore this is an instrumentation test that must be inserted under the AndroidTest folder.

Let’s now see how we can test the PlaylistItem in isolation:

@get:Rule
val composeTestRule = createComposeRule()

@Test
fun playlistItem_Render_Success() {
val playlist = Playlist("Playlist 1", "Description 1")

composeTestRule.setContent {
PlaylistItem(playlist = playlist)
}

composeTestRule.onNode(hasText(playlist.title)).assertIsDisplayed()
composeTestRule.onNode(hasText(playlist.description)).assertIsDisplayed()
}

In this test, we use the createComposeRule to set up the Compose test rule. We then render the PlaylistItem composable with a dummy Playlist object. After rendering, we use onNode to check if the playlist title and description are correctly displayed.

These automated tests use Jetpack Compose's testing libraries to verify if the PlaylistScreen and PlaylistItem composables render as expected. They help ensure that the UI is correctly displayed and the appropriate data is rendered, providing confidence in the correctness of your composable functions. Remember to import the necessary dependencies and adapt the test code to your specific project setup.

Conclusion

Declarative UI is the future both in the web and mobile platforms. All major players have already adopted it, and it looks like all the other UI generation tools will eventually become deprecated.

It introduces a paradigm shift in building the UI where the views are immutable, and their state cannot change. When the data state changes, the views are recreated from scratch and are put to display the data updates.

Declarative UI building and Jetpack Compose specifically offer advantages such as simpler code that is easier to read, write and maintain. As a bonus, we can get rid of Fragments while maintaining the advantage of reusable UI components.

Shipbook offers fantastic Jetpack Compose debugging capabilities. You can add logs to monitor any UI rendering errors. Those will enable you to track, trace and fix every issue efficiently and effectively.

The sooner you start getting your hands on it, the better!

· 15 min read
Kustiawanto Halim

Exception Handling

In modern mobile applications, it is common that the application is required to perform network calls. The developer needs to make sure this heavy work is not running in the main thread of the application, as this will block the UI, leading the application to be unresponsive and triggering an Application Not Responsive (ANR) error.

There have been many approaches to preventing apps from blocking, including threading, callbacks, futures, promises, and most importantly, coroutines.

Kotlin handles this issue effectively by delegating most functions to libraries and integrating coroutine support at the language level. A coroutine is a concurrency design concept that may be used on Android to facilitate asynchronous function operation. Based on the existing principles from other languages, coroutines were added to Kotlin in version 1.3.

Coroutines not only enable asynchronous programming—often known as non-blocking programming—but also offer up plenty of additional possibilities, such as concurrency and actors. Coroutines on Android manage lengthy operations that will otherwise render your app unresponsive by blocking the main thread.

This article discusses how to leverage coroutines in Kotlin to solve asynchronous programming challenges, which allows you to create clearer and more efficient code.

Alternatives to Coroutines

Before we get into coroutines, let's have a look at some of the alternative solutions.

Threading, or using a separate thread for long-running functions is the most well-known approach to avoid blocking the UI. However, it has several drawbacks: threading is costly, the number of threads is limited by your operating system, and debugging threads becomes yet another issue in multi-threaded programming.

Callbacks are another popular solution to concurrency problems. A callback is used to provide one function as a parameter to another function, which will then be called after the process is finished. Although it seems like a possible solution, It may cause callback hell which will make it hard to propagate and handle errors.

A future or promise (other languages use other names) entails a promise object that can be operated on after a function call. Using a promise requires us to use a specific design pattern and API to handle chaining calls. We also need to introspect the promise object to be able to get the real value being promised.

Kotlin's approach for working with asynchronous code is to adopt coroutines, instances of suspendable computations, in which a function can postpone its execution and restart it later. Promise and Coroutines differ primarily in that Promise returns an explicit result object, whereas Coroutines yields (which allows other functions to take control of the running thread), enabling us to suspend or resume a Coroutines. Coroutines aren’t really a new idea; in fact, they’ve been around for years and are common in several other programming languages, like Go.

One benefit of using a coroutine is that writing non-blocking code is almost the same as writing blocking code for developers. The programming paradigm itself does not change. Most of the functionality is delegated to libraries, and you won’t have to learn an entirely new set of APIs.

Kotlin Coroutines

Notable Features

There are several benefits to using coroutines:

  • Lightweight: Since suspending is supported, you can execute multiple coroutines on a single thread without blocking the thread where the coroutine is operating. Suspending, as opposed to blocking, saves memory while allowing for several concurrent tasks.
  • Fewer memory leaks: This is because coroutines run operations using structured concurrency inside a scope.
  • Built-in cancellation support: Cancellations seamlessly propagate across the running coroutine tree.
  • Jetpack integration: Various Jetpack libraries have extensions that offer comprehensive coroutine access; some libraries additionally offer their native coroutine scope for structured concurrency.

Coroutine Concepts

There are four main elements of a coroutine: dispatchers, CoroutineScope, jobs, and CoroutineContext.

Starting a Coroutine

There are two ways to start a coroutine:

  • launch creates a new coroutine but does not return the result to the caller; you can use it to start any job that is presumed "fire and forget."
  • async creates a new coroutine and lets you return a result using the await suspend method.

Because a normal function cannot call await, you should use the launch function to create a new coroutine from it. Only use async within another coroutine or within a suspend function.

Dispatchers

Coroutines in Kotlin require dispatchers to specify which threads will be used to execute a coroutine. You need to set Kotlin coroutines to the default or IO Dispatchers to run code outside of the main thread.

Kotlin offers three dispatchers that you can use to designate where the coroutine should run:

  • Dispatchers.Main launches a coroutine on the main Android thread; you can only use this for dealing with the user interface and executing short operations, e.g., updating the value of a TextView.
  • Dispatchers.IO is designed to handle disk and network I/O independently of the main thread, e.g., running any network activity.
  • Dispatchers.Default executes coroutines for CPU-intensive tasks outside of the main thread. E.g., parsing a huge JSON object.

CoroutineScope

CoroutineScope keeps track of each coroutine it generates using launch or async. In Android, several KTX libraries provide their CoroutineScope for specific lifecycle types. ViewModel, for example, has a viewModelScope, whereas Lifecycle has a lifecycleScope. However, unlike a Dispatcher, a CoroutineScope does not run the coroutine.

The following code will demonstrate how to create your CoroutineScope:

class MyCoroutineScope {

// To create a scope, we will combine a Job and Dispatchers.
val myScope = CoroutineScope(Job() + Dispatchers.Main)

fun printNumberAfterDelay() {
// Launch a new coroutine with given scope
myScope.launch {
// Delay the function for oneseveral seconds
delay(1000L)
// Print desire number
println(97)
}
}

fun cancel() {
// To cancel ongoing coroutines work, simply cancel the scope
myScope.cancel()
}
}

A scope that has been canceled cannot launch any new coroutine. As a result, you should always use scope.cancel() when the class that controls its lifecycle is about to be destroyed. When you use viewModelScope, the ViewModel class automatically cancels the scope in the ViewModel's onCleared() function.

Job

A Job is the coroutine's handler. Each coroutine you create using launch or async produces a Job instance that has a unique identifier and maintains the coroutine's lifecycle. You also could provide a Job to a CoroutineScope to control its lifecycle, as illustrated in the example below:

class MyCoroutineScope {

fun printNumberAfterDelay() {
// Newly launched coroutines will be assigned to job
val job = myScope.launch {
// Long running task
}

if (...) {
// If we cancel the coroutine launched above,
// it will not affect the scope where the coroutine launched in
job.cancel()
}
}
}

CoroutineContext

The behavior of coroutines can be defined using CoroutineContext. Several things you can configure within CoroutineContext are:

  • A job, to control the coroutine’s lifecycle
  • Dispatchers, to control in which thread the coroutine runs
  • A name for the coroutine, for easier debugging process
  • A CoroutineExceptionsHandler, to catch exceptions
class MyCoroutineScope {

val myScope = CoroutineScope(Job() + Dispatchers.Main)

fun printNumberAfterDelay() {
// Launch coroutine on Dispatchers.Main
val job1 = myScope.launch {
// CoroutineName = "coroutine" (default)
delay(1000L)
println(98)
}

// Launch a new coroutine on Dispatchers.IO
val job2 = myScope.launch(Dispatchers.IO + "RunningOnIO") {
// CoroutineName = "RunningOnIO" (overridden)
delay(2000L)
println(99)
}
}
}

A new Job instance is assigned to a new coroutine launched within a Scope, while the other CoroutineContext properties are inherited from the parent Scope. By providing a new CoroutineContext to the launch or async function, you can override the inherited properties.

Now, let's discuss how to use coroutines with a real example.

Creating a Background Task

A network request is a common use case for all applications. We cannot call a network request on the main thread because it will block it and cause an Application Not Responding (ARN) error. That's why we need to call network requests in the background thread, and to do that, we can use a coroutine.

Take a look at the following example of repository code:

class DownloadFileRepository(private val fileHelper: FileHelper) {

// Function that makes the network request, blocking the current thread
fun makeDownloadRequest(url: String): Result<String> {
val url = URL(url)
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "GET"
doOutput = true
fileHelper.save(inputStream)
return Result.Success(fileHelper.destinationPath)
}
return Result.Error(Exception("Cannot open HttpURLConnection"))
}
}

The makeDownloadRequest(url:) is a synchronous function that blocks the calling of the main thread. If you are not familiar with Result, it is basically just an encapsulation of the success value of generic type T or failure with a throwable exception.

Kotlin already provides the Result class, as defined in its documentation:

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

A ViewModel will have a function that calls the network request when the user performs an action, like that below:

class DownloadViewModel(
private val repository: DownloadFileRepository
): ViewModel() {

fun download(url: String) {
repository.makeDownloadRequest(url)
}
}

Calling DownloadViewModel.download(url:) will block the UI thread from which you’re making the request. We must avoid doing this since it will freeze the application UI so that the user cannot interact.

To make the download(url:) function run outside the main thread, create a new coroutine and run the function on an IO dispatcher as follows:

class DownloadViewModel(
private val repository: DownloadFileRepository
): ViewModel() {

fun download(url: String) {
// Launch network request on I/O Dispatchers
viewModelScope.launch(Dispatchers.IO) {
repository.makeDownloadRequest(url)
}
}
}

viewModelScope is the CoroutineScope included in the ViewModel KTX Extension. If you use ViewModel class, it’s recommended to start a coroutine in viewModelScope, which will be canceled; any running coroutine calls are also canceled when the ViewModel is destroyed.

When you call the download(url:) function from the View layer of the main thread, the launch function will create a new coroutine with IO dispatchers as the thread resolver. The download(url:) function will continue running until you get a success or failure response.

You already made sure that the download(url:) function does not block the main thread, but you still need to ensure that makeDownloadRequest(url:) will always be called from the IO dispatchers each time you want to use it. So, let’s refactor our code to make sure makeDownloadRequest(url:) is also a main-safe function.

Making Coroutine Main-Safe Functions

A function that does not block UI updates on the main thread is a main-safe function. makeDownloadRequest(url:) is not main-safe, as calling it from the main thread blocks the UI update.

To make this function main-safe, use the withContext(Dispatchers.IO) function provided by the coroutine to change the resolver thread to an IO Thread. Adding withContext will also make the makeDownloadRequest(url:) function become a suspend function. The keyword “suspend” is the way Kotlin forces us to call the function marked within a coroutine call.

Refactoring the code will look like the following:

class DownloadFileRepository(private val fileHelper: FileHelper) {

suspend fun makeDownloadRequest(url: String): Result<String> {
// Add coroutine context to IO Dispatchers
withContext(Dispatchers.IO) {
val url = URL(url)
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "GET"
doOutput = true
fileHelper.save(inputStream)
return Result.Success(fileHelper.destinationPath)
}
return Result.Error(Exception("Cannot open HttpURLConnection"))
}
}
}

Now we do not need to specifically define the dispatcher in ViewModel layer, since we already moved the execution to a repository layer. Take a look at the refactored code:

class DownloadViewModel(
private val repository: DownloadFileRepository
): ViewModel() {

fun download(url: String) {
// We do not need to specify thread anymore
viewModelScope.launch {
val result = repository.makeDownloadRequest(url)

when (result) {
is Result.Success<DownloadResponse> -> // handle success response
else -> // handle error response
}
}
}
}

With the refactored code, we do not specify the dispatchers on the download(url:) function anymore. The coroutine called from viewModelScope is now running in the main thread but it is not blocking the UI update because makeDownloadRequest(url:) is running in the IO Dispatchers.

We also add some response handlers to the network request function, but is it safe from exception?

Exception Handling

Our repository layer can still throw an exception because it has a function to write network responses to a file. We can add try-catch function to handle this kind of exception as follows:

class DownloadViewModel(
private val repository: DownloadRepository
): ViewModel() {

fun download(url: String) {
viewModelScope.launch {
// Add try-catch to handle exceptions
val result = try {
repository.makeDownloadRequest(url)
} catch(e: Exception) {
Result.Error(Exception("Network request failed"))
}

when (result) {
is Result.Success<DownloadResponse> -> // handle success response
else -> // handle error response
}
}
}
}

Now, any unexpected error thrown from the repository layer will be treated as "Network request failed" and can be handled by the UI layer safely.

Best Practices

In this section, we’ll discuss the best practices for using coroutines to make your applications scalable and testable.

Suspend Function Must Be Main-Safe

A function must be main-safe if it is to be treated as a suspend function. If a class is responsible for long-running operations like network calls, that class is also responsible to move its execution from the main thread using withContext. In our example above, we already refactored our repository class (which is responsible for network calls) to call its function from the IO Thread.

Coroutines in ViewModel

If you use the ViewModel class in your applications, it is responsible for creating and launching the coroutine, instead of exposing it and launching it in the view layer. You should also use viewModelScope to create the coroutine since it will handle its lifecycle scope based on the ViewModel lifecycle scope:

class DownloadViewModel(
private val repository: DownloadRepository
): ViewModel() {

// DO THIS, create coroutine based on viewModelScope
fun download(url: String) {
viewModelScope.launch {
repository.makeDownloadRequest(url)
}
}

// DON'T DO THIS, do not make download suspend
suspend fun download(url: String) =
repository.makeDownloadRequest(url)
}

Making Coroutines Cancelable

A coroutine is not canceled when its job is canceled. If you block an operation in a coroutine call, you must make sure the coroutine is cancelable. To make sure it’s not canceled before calling it, we can add an ensureActive function.

class DownloadViewModel(
private val repository: DownloadRepository
): ViewModel() {

fun download(url: String) {
viewModelScope.launch {
// Add try-catch to handle exceptions
val result = try {
ensureActive() // Make sure not canceled
repository.makeDownloadRequest(url)
} catch(e: Exception) {
Result.Error(Exception("Network request failed"))
}

when (result) {
is Result.Success<DownloadResponse> -> // handle success response
else -> // handle error response
}
}
}
}

The Dispatcher Should Be Injected

When creating a new coroutine or calling it inside withContext, it is recommended to inject the dispatchers instead of hardcoding it. This will make your code testable when you want to test the coroutine by changing it to TestDispatchers, which we will discuss in a later section.

Your repository layer code will be refactored as follows:

class DownloadFileRepository(
Private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
private val fileHelper: FileHelper
) {

suspend fun makeDownloadRequest(url: String): Result<String> {
// now we call inside injected dispatchers
withContext(dispatcher) {
val url = URL(url)
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "GET"
doOutput = true
fileHelper.save(inputStream)
return Result.Success(fileHelper.destinationPath)
}
return Result.Error(Exception("Cannot open HttpURLConnection"))
}
}
}

Testing Coroutines

When testing the coroutine function, you need to inject TestDispatcher into the coroutine dispatcher.

kotlinx-coroutine-test has two implementations of TestDispatcher:

  • StandartTestDispatcher will run the coroutine with a scheduler when the test thread is ready. Use this dispatcher to simulate a queue like a “real” dispatcher.
  • UnconfinedTestDispatcher will run the coroutine eagerly and write the test more easily. However, it gives you less control when the coroutine is executed during the test.

The runTest function tests a coroutine, while the runTest function will use a TestCoroutineScheduler to skip the delay in the suspend function tested. Take a look at the following sample test class:

    class DownloadFileRepositoryTest {

@Test
fun testMakeDownloadRequest() = runTest {
// Create a test dispatcher and inject it to the repository
val testDispatcher = UnconfinedTestDispatcher(testScheduler)

val fileHelper = FakeFileHelper()
val repository = DownloadFileRepository(
testDispatcher,
fileHelper
)

repository.makeDownloadRequest("http://www.shipbook.io")
assertThat(fileHelper.destinationPath.isNotEmpty())
}
}

When creating a test class, make sure to share TestDispatcher with the same scheduler. Doing this will allow your coroutine to run on a single thread to make your test deterministic.

Conclusion

Coroutines offer a lightweight and easy-to-use way to handle multi-threaded programming in Kotlin, as well as in Android development. Since coroutines are fully supported by Android and Kotlin itself, it is highly recommended that you use coroutines when dealing with asynchronous programming. Remember, although it is convenient to use coroutines, you also need to make sure your application handles the suspend function in the correct way.

Due to the fact that we can't really determine which instructions are being executed at any particular time, asynchronous programming can occasionally produce errors. If we want to debug asynchronous code, we may utilize logs. Shipbook captures your application logs and exceptions, allowing you to remotely gather, monitor, and evaluate your user mobile-app logs and crashes in the cloud on a per-user and session basis. This will help you to quickly examine relevant data and solve errors.

· 9 min read
Nikita Lazarev-Zubov

Exception Handling

The first version of Java was released in 1995 based on the great idea of WORA (“write once, run anywhere”) and a syntax similar to C++ but simpler and human-friendly. One notable language invention was checked exceptions—a model that later was often criticized.

Let’s see if checked exceptions are really that harmful and look at what’s being used instead in contemporary programming languages, such as Kotlin and Swift.

Good Ol’ Java Way

Java has two types of exceptions, checked and unchecked. The latter are runtime failures, errors that the program is not supposed to recover from. One of the most notable examples is the notorious NullPointerException.

The fact that the exception is unchecked doesn’t mean you can’t handle it:

Object object = null;
try {
System.out.println(object.hashCode());
} catch (NullPointerException npe) {
System.out.println("Caught!");
}

The difference between a checked and unchecked exception is that if the former is raised, it must be included in the method’s declaration:

void throwCustomException() throws CustomException {
throw new CustomException();
}

static class CustomException extends Exception { }

The compiler will make sure that it’s handled— sooner or later. The developer must wrap the throwCustomException() with a try-catch block:

try {
throwCustomException();
} catch (CustomException e) {
System.out.println(e.getMessage());
}

Or pass it further:

void rethrowCustomException() throws CustomException {
throwCustomException();
}

What’s Wrong with the Model

Checked exceptions are criticized for forcing people to explicitly deal with every declared exception, even if it’s known to be impossible. This results in a large amount of boilerplate try-catch blocks, the only purpose of which is to silence the compiler.

Programmers tend to work around checked exceptions by either declaring the method with the most general exception:

void throwCustomException() throws Exception {
if (Calendar.getInstance().get(Calendar.DAY_OF_MONTH) % 2 == 0) {
throw new EvenDayException();
} else {
throw new OddDayException();
}
}

Or handling it using a single catch-clause (also known as Pokémon exception handling):

void throwCustomException()
throws EvenDayException, OddDayException {
// ...
}

try {
throwCustomException();
} catch (Exception e) {
System.out.println(e.getMessage());
}

Both ways lead to a potentially dangerous situation, when all possible exceptions are sifted together, including everything that is not supposed to be dismissed. Error-handling blocks of code also become meaningless, fictitious, if not empty.

Even if all exceptions are meticulously dealt with, public methods swarm with various throws declarations. This means all abstraction levels are aware of all exceptions that are thrown around it, compromising the principle of information hiding.

In some parts of the system, where multiple throwing APIs meet, a problem with scalability might emerge. You call one API that raises one exception, then call another that raises two more, and so on, until the method must deal with more exceptions than it reasonably can. Consider a method that must deal with these two:

void throwsDaysExceptions() throws EvenDayException, OddDayException  {
// …
}
void throwsYearsExceptions() throws LeapYearException {
// …
}

It's doomed to have more exception-handling code than business logic:

void handleDate() {
try {
throwsDaysExceptions();
} catch (EvenDayException e) {
// ...
} catch (OddDayException e) {
// ...
}
try {
throwsYearsExceptions();
} catch (LeapYearException e) {
// ...
}
}

And finally, the checked exception approach is claimed to have a problem with versioning. Namely, adding a new exception to the throws section of a method declaration breaks client code. Consider the throwing method from the example above. If you add another exception to its throws declaration, the client code will stop compiling:

void throwException()
throws EvenDayException, OddDayException, LeapYearException {
// ...
}

try {
// Unhandled exception: LeapYearException
} catch (EvenDayException e) {
// ...
} catch (OddDayException e) {
// ...
}

The Kotlin Way

Sixteen years after Java was first released, in 2011, Kotlin was born from the efforts of JetBrains, a Czech company founded by three Russian software engineers. The new programming language aimed to become a modern alternative to Java, mitigating all its known flaws.

I don’t know any programming language that followed Java in implementing checked exceptions, Kotlin included, despite the fact it targeted JVMs. In Kotlin, you can throw and catch exceptions similarly to Java, but you’re not required to indicate an exception in a method’s declaration. (In fact, you can’t):

class CustomException: Exception()

fun throwCustomException() {
throw CustomException()
}

fun rethrowCustomException() {
try {
throwCustomException()
} catch (e: CustomException) {
println(e.message)
}
}

Even catching is up to the programmer:

fun rethrowCustomException() {
throwCustomException() // No compilation errors.
}

For interoperability with Java (and some other programming languages), Kotlin introduced the @Throws annotation. Although it’s optional and purely informative, it’s required for calling a throwing Kotlin method in Java:

@Throws(CustomException::class)
fun throwCustomException() {
throw CustomException()
}

From One Extreme to Another

It may seem that programmers can finally breathe easy, but, personally, I think by solving the original problem, this new approach—Kotlin’s exceptions model—creates another. Unscrupulous developers are free to entirely ignore all possible exceptions. Nothing stops them from quickly wrapping a handful of exceptions with a try-catch expression and shipping the result to their end users, with a prayer. Or not covered exceptions are going to be discovered by end users.

Even if you’re a disciplined engineer, you’re not safe: Neither the compiler nor API will alert you to exceptions lurking inside. There’s no reliable way to make sure that all possible errors are being properly handled.

You can only guard yourself from your own code, patiently annotating your methods with @Throws. Though, even in this case, the compiler will tell you nothing and it’s easy to forget one exception or another.

The Swift Way

Swift first appeared publicly a little later, in 2014. And again, we saw something new. The error-handling model itself lies somewhere between Java’s and Kotlin’s, but how it works together with the language’s optionals is incredible. But first things first.

Of course, Swift has runtime, “unchecked”, errors—an array index out of range, a force-unwrapped optional value turned out to be nil, etc. But unlike Java or Kotlin, you can’t catch them in Swift. This makes sense since runtime exceptions can only happen because of a programming mistake, or intentionally (for instance, by calling fatalError()).

The rest of exceptions are errors that are explicitly thrown in code. All methods that throw anything must be marked with the throws keyword, and all code that calls such methods must either handle errors or propagate them further. Looks familiar, doesn’t it? But there’s a catch.

Fly in the Ointment

Let’s look at an example from above:

func throwError() throws {
if (Calendar.current.component(.day, from: Date()) % 2 == 0) {
throw EvenDayError()
} else {
throw OddDayError()
}
}

As you can see, you don’t declare specific errors that a method can throw; you’re only required to mark it as throwing something. The consequence of this is that you, again, don’t really know what to catch.

Unfortunately, the code below won’t compile:

do {
/*
Errors thrown from here are not handled because the enclosing
catch is not exhaustive
*/
try throwError()
} catch is EvenDayError {
print(String(describing: EvenDayError.self))
} catch is OddDayError {
print(String(describing: EvenDayError.self))
}

You always have to add Pokémon handling:

do {
try throwError()
} catch is EvenDayError {
print(String(describing: EvenDayError.self))
} catch is OddDayError {
print(String(describing: EvenDayError.self))
} catch {
print(error)
}

In fact, the Swift compiler doesn’t care about specific error types that you try to catch. You can even add a handler for something entirely irrelevant:

do {
try throwError()
} catch is EvenDayError {
print(String(describing: EvenDayError.self))
} catch is IrrelevantError {
print(String(describing: EvenDayError.self))
} catch {
print(error)
}

Or you can have only one default catch block that covers everything:

    do {
try throwError()
} catch {
print(error)
}

Another bad thing about the approach is that, without a workaround, you can’t catch one error and propagate another. The only way to implement such behavior is to catch the error you’re interested in and throw it again:

func rethrow() throws {
do {
try throwError()
} catch is EvenDayError {
throw EvenDayError() // Here's the trick.
} catch is IrrelevantError {
print(String(describing: EvenDayError.self))
} catch {
print(error)
}
}

Ointment

In my opinion, Swift’s strongest merit is its optionals system that cooperates with all aspects of the language. If you don’t care about thrown errors, instead of fictitious catch-blocks, you can always write try? Execution of the method will stop the moment the error is thrown, without propagating it further:

try? throwError()

If you’re feeling bold, you can use try! instead of try?, which won’t suppress the error if it’s thrown, but will let you omit the do-catch block:

try! throwError()

This method also allows converting a throwing call to a value. try? will give you an optional one, whereas try! has an effect similar to force-unwrapping:

func intOrError() throws -> Int {
// …
}

let optionalInt = try? intOrError() // Optional(Int)
let dangerousCall = try! intOrError() // Int or die!

Conclusion

Personally, I find Kotlin’s way, ahem, a failure. I can understand why Kotlin developers decided not to follow Java in its way of checked exceptions, but ignoring exceptions entirely, without a hint of static checks, is too much.

On the other hand, is the Java way really that harmful? No mechanism can defend software from undisciplined programmers. Even the best idea can be distorted and misused. But applying Java’s principles as designed can lead to good results.

Connecting two levels of abstraction, you can catch errors from one level and re-throw new types of errors to propagate them to the next level. You can catch several types of errors, “combine” them into one another, and throw them for further handling. This can help mitigate problems with encapsulation and scalability. For instance:

void throwCustomException() throws CustomException {
try {
throwDayException();
} catch (EvenDayException | OddDayException e) {
throw new CustomException();
}
}

What Java lacked from the very beginning is Swift’s optionality system and a syntax to bind exception handling and optional values. I believe, coupled with entirely static checks of thrown exceptions, this would build a very strong model that can satisfy the grouchiest programmers. Although, in any aforementioned programming language, this would require breaking changes, I personally believe it would be a game-changing improvement of code safety.

And if you want to improve your app stability right now, Shipbook is already here for you! It proactively inspects your app, catches exceptions and allows you to analyze them even before your users stumble upon the problem.

· 13 min read
Donald Le

Unit Testing in Android Development

Introduction

Unit testing entails the testing of the smallest parts of software, such as methods or classes. The main role of unit testing is to make sure the isolated part works as expected without integrating with third-party software, databases, or any dependency. To achieve this, software developers implement multiple testing techniques, like using stubs, mocks, dummies, and spies.

This post will show you why you should perform unit testing and how to implement it in your Android development project.

Benefits of Unit Testing

Unit testing allows you to catch software bugs early in the software development process, instead of QA finding them in the integration phase or end-to-end-testing, or, even worse, in the production environment. Moreover, as you develop your product, more features are added, meaning integration tests and end–to-end tests alone cannot cover all the corner cases. With unit testing, more corner cases are covered, which ensures your product meets the expected quality.

Benefits of Test-Driven Development (TDD)

Unit testing often goes along with the test-driven development (TDD) methodology, where developers first write the test, then write the feature code. At first, the tests will fail because the feature is not yet implemented. When the feature code is implemented, the tests will become green.

The huge benefit of TDD is that a software team can make sure the product is built and will meet the expected requirements, as demonstrated by the tests. Moreover, because developers write the tests first, they need to spend more time thinking about the product and what features the product has to cover; this way, the product being built will tend to have a higher quality.

Also, writing tests before writing product code will prevent developers from needing to refactor the code just to be able to write tests for it. For example, in the Go language, if the developers do not implement code with an interface, it’s very hard to write tests later on.

Example Application to Demonstrate Unit Testing

To better understand how to apply proper testing techniques for Android applications, let’s get your hands dirty by building a real application and then write tests for it. The application will show a list of popular movies for users to choose from as suggestions for their weekly movie night. Check out this GitHub repository for the full application code.

After opening the application, users will see a list of popular movies:

The movie suggestion application shown on a virtual device

Figure 1: The movie suggestion application shown on a virtual device

You can then tap on a movie for details like its plot summary and cast:

Details for the movie “Black Rock”

Figure 2: Details for the movie “Black Rock”

Unit Testing (Local Testing)

The unit test of our application will be run by a popular test runner called JUnit, a unit-testing framework that uses JVM languages like Java or Kotlin. If you’re not familiar with JUnit, you can learn more about it here. It helps you structure your tests, like what needs to be done first, what will be done last to clean data, and which data should be collected for the test report.

An Example of a Simple Unit Test

Okay, now let’s write an example unit test for the application.

We have the MovieValidator class in the utils package, which has the function isValidMovie:

import android.text.Editable
import android.text.TextWatcher
import java.util.regex.Pattern

class MovieValidator : TextWatcher {
internal var isValid = false
override fun afterTextChanged(editableText: Editable) {
isValid = isValidMovie(editableText)
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) = Unit
companion object {
private val MOVIE_PATTERN = Pattern.compile("^[a-zA-Z]+(?:[\\s-][a-zA-Z]+)*\$")
fun isValidMovie(movie: CharSequence?): Boolean {
return movie != null && MOVIE_PATTERN.matcher(movie).matches()
}
}
}

To write the unit test for the function isValidMovie, we will first create a test class called MovieValidatorTest in the test folder. Then, we will need to import the MovieValidator class to test the isValidMovie in it.

The MovieValidatorTest will look like the following:

import com.fernandocejas.sample.core.functional.MovieValidator
import org.junit.Assert.assertTrue
import org.junit.Assert.assertFalse
import org.junit.Test
import mu.KotlinLogging
class MovieValidatorTest {
private val logger = KotlinLogging.logger {}
@Before
fun setUp() {
logger.info { "Starting the isValidMovie test" }
}
@Test
fun isValidMovie() {
assertTrue(MovieValidator.isValidMovie("The lord of the rings"))
assertFalse(MovieValidator.isValidMovie("name@email"))
}
@After
fun tearDown(){
logger.info { "Finishing the isValidMovie test" }
}
}

In the test file above, we implemented one test case to check the validity of the movie name. We also apply Before and After annotations to adding logging information so that we know when the test is about to start and when it is about to finish.

The Before and After annotations, help us structure our test scenario better. The Before annotation will be executed before every test, and the After annotation will be executed after every test. Developers often use these for setting up data for tests and then cleaning it up after testing is complete.

Notes: In order to install the logger library, we need to add the following code into our gradle configuration file.

implementation 'io.github.microutils:kotlin-logging-jvm:2.0.11'

When we run the test, we will see results as below:

Tests passed for movie validator test case

Figure 3: Tests passed for movie validator test case

The example unit test we just went over is very simple. But in real-world applications, you’ll need to deal with all kinds of dependencies and third-party APIs. How can we write tests for functions that interact with third-party dependencies?

When implementing unit testing, the best practice is to not deal with the real thing, like the real database, the real response from another API we take as input for the function, or any third-party dependencies. The reason for this is that when it comes to unit testing, we want to isolate the tests so that each test will test each unit. We could test the database or the third-party dependencies, but this will lead to flakiness in the tests. Instead, we’ll use “test doubles,” objects that stand in for the real objects when we implement the test. There are five types of test doubles: fake, dummy, stub, spy, and mock.

In this article, we’ll review the stub and mock types and use them for our example application.

  • Stubs provide fake data to the test.
  • Mocks check whether the expectation of the unit we are testing has been met.

How to Create Stubs and Mocks in a Sample Project

To better understand how to use a stub and a mock, let’s apply these techniques for writing unit tests in our movie suggestion app using MockK.

MockK is the well-known mock library in Kotlin, which provides native support for the Kotlin language. Users who are fond of the syntactic sugar of Kotlin will still be able to enjoy it with MockK. Moreover, since the default class and properties in Kotlin are final, using Mockito is considerably hard when mocking in Kotlin. But with MockK’s support, users won’t have to deal with that challenge anymore. To learn more about the benefits of using MockK over Mockito, check out this article.

To include the MockK library in your Android project, we need to add this line into the build.gradle.kts file:

testImplementation(TestLibraries.mockk)

The TestLibraries.mockk value is defined in Dependencies.kt as:

const val mockk = "io.mockk:mockk:${Versions.mockk}"
const val mockk = "1.10.0"

And that’s it.

So, let’s say we’re trying to test the class GetMovieDetails.

Initially, we usually implement the code without dependency injection like the following:

class GetMovieDetails : UseCase<MovieDetails, Params>() {
private val moviesRepository = MoviesRepository()
override fun run(params: Params) = moviesRepository.movieDetails(params.id)
data class Params(val id: Int)
}

The MovieRepository class is as defined below:

class MoviesRepository {
lateinit var context: Context
lateinit var retrofit: Retrofit
private val networkHandler = NetworkHandler(context)
private val service = MoviesService(retrofit)
fun movieDetails(movieId: Int): Either<Failure, MovieDetails> {
return when (networkHandler.isNetworkAvailable()) {
true -> request(
service.movieDetails(movieId),
{ it.toMovieDetails() },
MovieDetailsEntity.empty
)
false -> Left(NetworkConnection)
}
}
}

But writing code like this makes writing unit tests for this class impossible since we cannot mock dependency for the MoviesRepository class. Well, actually, we can write unit tests literally, but we’d need to use the real movie database, and this would lead to slower tests and make your test couple with third-party dependencies. Moreover, the problem with third-party dependencies is that they might not be working for other reasons, not because of our code.

The best practice when it comes to writing code that can be tested is applying dependency injection, which you can learn more about here.

First, we need to change the class MovieRepository to an interface type. The code for the interface MovieRepository will be changed as below:

interface MoviesRepository {
fun movies(): Either<Failure, List<Movie>>
fun movieDetails(movieId: Int): Either<Failure, MovieDetails>
class Network
@Inject constructor(
private val networkHandler: NetworkHandler,
private val service: MoviesService
) : MoviesRepository {
override fun movieDetails(movieId: Int): Either<Failure,
MovieDetails> {
return when (networkHandler.isNetworkAvailable()) {
true -> request(
service.movieDetails(movieId),
{ it.toMovieDetails() },
MovieDetailsEntity.empty
)
false -> Left(NetworkConnection)
}
}
}
..
}

Then, the class GetMovieDetails will be written as below, with the constructor MovieRepository:

class GetMovieDetails {
@Inject constructor(private val
moviesRepository:MoviesRepository):
UseCase < MovieDetails, Params > () {
override fun run(params: Params) = moviesRepository.movieDetails(params.id)
data class Params(val id: Int)
}
}

In order to test this class without calling the real database, we need to mock the MoviesRepository class using MockK:

@MockK private lateinit var moviesRepository: MoviesRepository

The test function for the movieDetails function will be written as below:

class GetMovieDetailsTest : UnitTest() {
private lateinit var getMovieDetails: GetMovieDetails
@MockK private lateinit var moviesRepository:
MoviesRepository
@Before fun setUp() {
getMovieDetails = GetMovieDetails(moviesRepository)
every { moviesRepository.movieDetails(MOVIE_ID) } returns
Right(MovieDetails.empty)
}
@Test fun `should get data from repository`() {
getMovieDetails.run(GetMovieDetails.Params(MOVIE_ID))
verify(exactly = 1) {
moviesRepository.movieDetails(MOVIE_ID)
}
}
companion object {
private const val MOVIE_ID = 1
}
}

In the setUp step, with @Before annotation, we initialize the getMovieDetails variable.

Then in the test function, we call the run function, with the input as GetMovieDetails.Params(MOVIE_ID. After that, we use the verify function, provided by MockK to check whether or not the call was actually made exactly one time.

Now, we will run the test to see whether it works or not. To run the test in Android Studio, click on the green button on the test method:

Log for the unit test run when testing GetMovieDetails class

Figure 4: Log for the unit test run when testing GetMovieDetails class

Advantages and Disadvantages of Unit Testing

With unit tests in place, we can be confident that our logic is met and we will be notified if any changes are made that break the existing logic. In addition, the unit tests are run blazingly fast. Still, we’re not sure if users can interact with the application as we expect.

That’s where UI testing comes into play.

UI Testing (Instrumentation Testing)

Traditionally, automated end-to-end testing is usually done in a blackbox way, meaning we create another project for automated end-to-end testing of the application. We need to find the locator of the elements in our application and find a way to interact with it via a framework such as Appium or UIAutomator. However, this approach is more time-consuming since we have to redefine the locators of the elements in our application; also, Appium is pretty slow when interacting with the real mobile application.

To be able to resolve the drawbacks of Appium, we’ll apply instrumentation tests with the help of the Espresso and AndroidX frameworks.

How to Implement UI in a Project

Let’s say we want to check whether the movie list button is shown and is clickable.

The MoviesActivity is defined as following:

class MoviesActivity : BaseActivity() {
companion object {
fun callingIntent(context: Context) = Intent(context, MoviesActivity::class.java)
}
override fun fragment() = MoviesFragment()
}

The actual logic and how the movies page is rendered is defined in the MoviesFragment class:

@AndroidEntryPoint
class MoviesFragment : BaseFragment() {
...
private fun loadMoviesList() {
emptyView.invisible()
movieList.visible()
showProgress()
moviesViewModel.loadMovies()
}

private fun renderMoviesList(movies: List<MovieView>?) {
moviesAdapter.collection = movies.orEmpty()
hideProgress()
}
...
}

The test class will be written like the following:

class MainApplicationTest {
@get:Rule
val mActivityRule = ActivityTestRule(MoviesActivity::class.java, true, false)

@Before
fun setUp() {
mActivityRule.launchActivity(null)
Intents.init();
}

@After
fun tearDown() {
Intents.release();
}

@Test
fun clickMovieListButton() {
val movieListButton = onView(withId(R.id.movieList))
movieListButton.perform(click())
val moviePoster = onView(withId(R.id.moviePoster))
moviePoster.check(matches(isDisplayed()))
}
}

In the test class, we need to specify the activity of the application we want to run, in this case, MovieActivity.

  @get:Rule
val mActivityRule = ActivityTestRule(MoviesActivity::class.java, true, false)

Before the test is run, the activity will be initialized.

  @Before
fun setUp() {
mActivityRule.launchActivity(null)
Intents.init();
}

Then after the test is done, we will close the activity.

  @After
fun tearDown() {
Intents.release();
}

For the test itself, we find the movieList element, and click on it.

  @Test
fun clickMovieListButton() {
val movieListButton = onView(withId(R.id.movieList))
movieListButton.perform(click())
val moviePoster = onView(withId(R.id.moviePoster))
moviePoster.check(matches(isDisplayed()))
}

After running the test by clicking on the green button, we can see the test has passed:

Test result for instrumentation testing

Figure 5: Test result for instrumentation testing

Advantages and Disadvantages of Instrumentation Tests

So, with instrumentation tests, we can be confident that users can interact with the UI and the functionalities work as expected per our business requirements. And the speed is pretty amazing.

But the drawback of instrumentation tests is that after every change in production code, you will need to change the test code since the test is affected by both the user interface and the business logic.

Conclusion

Creating a working Android application is not a hard task. But to be able to create a high-quality application that’s reliable over time is very difficult. You need to run a lot of tests, from unit tests and integration tests to end-to-end tests. Each test has its own role to play in the success of your product. Creating tests not only ensures high quality, but also gives developers the confidence they need to add new features later on without worrying that new code will break existing functionality. So make sure you implement all of them before releasing your application on the market.

Still, writing tests is a daunting task, so you also need to take your time implementing them. Moreover, debugging tests to know why they failed requires much time and effort too. If you’re having a hard time debugging your tests, or even get stuck in them, check out Shipbook, a logging platform that can help you quickly debug issues in your tests. Shipbook provides numerous resources and documents to help you test your applications, along with logs to easily discover the root cause of that bug you’re struggling with.

· 13 min read
Yossi Elkrief
Elisha Sterngold

Yossi Elkrief

Interview with Mobile Lead at Nike, Author, Google Developers Group Beer Sheva Cofounder, Yossi Elkrief

Thank you for being with us today Yossi, would you like to begin with sharing a little bit about your position at Nike, and what you do?

I joined Nike for a bit more than two years now. I am head of mobile development in the Digital Studio of innovation. It is a bit different from regular app development but we still work closely with all the teams in WHQ, Nike headquarters in the US as well as Europe, China, and Japan. We really work across the globe, and we do some pretty cool things in the realm of innovation. We develop new technologies and try to find ways to harness new technologies or algorithms to help Nike provide the best possible service to our consumers.

I have experience in mobile for the past 13, almost 14 years now. I’ve been involved in Android development since their very first Beta release, even a bit before that. I also worked on iOS throughout the years, and I’ve been involved in a couple of pretty large consumer based companies and startups.

At Nike we have a few apps, such as: Nike Training Club (NTC), Nike Running Club (NRC), and the Nike app made for consumers, where you can purchase Nike’s products.

We work with all of those teams and other teams within Nike, on various apps as well as in-house apps that are specific creations of our studio, where we work on creating new innovative features for Nike.

One major project that is currently working on completing roll out is Nike Fit, recently launched in China and Japan. Nike Fit, is aimed at helping people shop Nike shoes online and hopefully for other apparels in the near future.

How is it working for Nike, as a clothing company, with a background of working mainly for tech companies?

Nike is a company with so much more technology than people realize. We are not just a shoe company or a fashion company.

Our mission is to bring inspiration and innovation to every athlete1 in the world.

We use a tremendous amount of technology to transform a piece of fabric into a piece in the collection of the Nike brand. Nike may be more faceforward than companies that I’ve worked for in the past, but there is a vast array of technologies that we work with in Nike, or work on building upon, to make Nike the choice brand for our customers, now and in the future.

One of the highest priorities at Nike is the athlete consumers. Because Nike is a brand that is specifically designed and geared toward athletes. We therefore try to keep all of Nike’s athletes at the forefront in terms of their importance to the company. Consumer facing, most of Nike’s products are not the apps. All of my previous experiences in app companies or technical companies that provide a service are pretty different from what I focus on now at Nike. So everything we do at Nike, all the services we provide, are to help serve athletes in their day to day activities, whether this be in sports for professional athletes, or for people with hobbies like running, football, or cycling and so on.

Everything I focus on has to do with providing athletes with better service while choosing their shoes, pants, or all the equipment they need, and that Nike provides so they can best utilize their skills.

Can you tell us a bit about what went into writing your book “Android 6 Essentials”? Do you feel that writing the book improved your own skills as a developer?

I write quite a lot. I don’t get to write as many technical manuals as I’d like, but I do write quite a few documentations, technical documents, and blog posts. Writing the book was a different process, but I really wanted to engage a technical audience, as this audience is very different from that of a poem, or story, which is less for use and more for enjoyment.

Writing the book made me a better person in general because I was working full time in the capacity of my position at the company that I was with at the time, and then on top of all of my regular responsibilities, in order to be able to keep to schedule and hit all of the milestones, and points that I wanted to cover in my book. I had to be very organised and devoted to the project. I had to juggle work, and family, and all of my other responsibilities as well, so I divided my time to make sure I could meet all of my goals. The process was really quite fun because in the end I had something that I built and created from scratch.

I would recommend it, because it gives you an added value that no one else will have, and in the end you have a final product that you can show someone, and say that it was your creation. I think the whole process makes you a better developer, and it helps you understand technology better, because you need to understand technology at a level and to a degree of depth in order to then explain it in writing to someone else.

You also took part in co-founding Google Developers Group Beer Sheva, which is also about sharing knowledge and bettering yourselves as developers, can you tell us a little about that process?

The main aim of Google Developers Group is sharing knowledge. When we share knowledge we can learn from everyone. Even if I built each of the pieces of a machine myself, when I share it with someone else, they can always bring to light something that I was unaware of; some new and interesting way of using it. Sharing with people helps more than just the basic value of assistance. Finding a group of peers that share the same desire or passion for technology, knowledge, and information, this is a key concept in growth, for everyone in general.

On that note, we are seeing an interesting trend in development: even though mobile apps are becoming increasingly more complex, the industry has succeeded in reducing the occurrence of crashes. Is that your experience as well and if so, what are, in your eyes, the main reasons for this shift?

It’s really a two part answer.

Firstly, both Google and Apple are providing a lot more information, and are focusing a lot more on user experience in terms of crashes, app not responding, bad reviews etc. Users are more likely to write a good review if you provide more information, or create a better service with more value for them. Consumers in general are more interested in using the same app, the same experience, if they love it. So they will happily provide you with more information so that you can solve its issues, and keep using your app rather than trying something new. We call them Power Users or Advanced Users. With their help, we can keep the app updated and solve issues faster.

The second part of the answer is that all of the tools, ID integrations, shared knowledge, documentation, has been vastly improved. People understand now that they need to provide a service that runs smoothly with as little interference as possible for the user and they do their part to make sure that these issues remain as low as possible in the apps. We want a crash rate lower than 0.1%. So we work 90% of our time to build an infrastructure that will remain robust and maintain top quality, with a negligible amount of crashes, exceptions, and app issues, in general, that will harm and affect the user experience.

Do you believe that all bugs should always be fixed? If not, do you have ways of defining which ones do not need to be fixed?

As a perfectionist, yes, we want to solve all of the app issues. But in terms of real life, we work with a simple process. We look at the impact of the bug. How many users are being impacted? What is the extent to the impact? What does the user have to do in order to use the service? Is it just a simple work around or is it preventing the user from using an important part of the app?

Do you close insignificant issues, or are they kept open in a back office somewhere?

No, so we are very careful and organized about all of the issues that we have in the system. We document every issue with as much information as possible. Sometimes you can fix an issue with dependency and provide a new version for some dependencies and then because of all the interactions of the code versions you have some issues being solved even though you didn’t do anything. So for example, this doesn’t happen much, but sometimes we have issues in the backlog that can remain unsolved for more than a month.

What is your view on QA teams? Some companies have come out saying that they don’t use QA teams and instead move that responsibility to the developer team. Do you believe that there should be a QA Team?

I believe that companies should have a Quality Assurance team, which is sometimes also called QE, Quality Engineering. I think as a developer, working on various platforms, when you implement a new feature or service, give or take on the architecture of the technology, the actual issue can be quite difficult to find. This requires a different point of view than the developer. When you develop or write the code, you have a different point of view in mind then users often have when it comes to using the app. 90% of the time users will actually often behave differently than developers anticipated when writing the code. So when we design the feature, sometimes we need to understand a bit better how users will interact. We have a product team that we involve and engage on an hourly basis. The same goes for QA. We use QA in our Innovation Studio as well, but the same goes for our apps. We are constantly engaging QA to see how to both resolve issues and understand better how the user will interact with the app.

What is your position on Android Unit testing: How are the benefits compared to the efforts?

With testing in general, some will say it's not necessary at all and will just rely on QA. I don’t side with either. I think it is a mix. You don't need to unit test every line of code. I think that is excessive. Understanding the architecture is more important than unit testing. It's more important to understand how and why the pieces of the puzzle interact- to understand why to choose one flow over another, than to just unit test every function. Sometimes pieces of the puzzle are better understood with unit testing, but it is not necessary to unit test everything. That said, the majority of our code does undergo UI and UX testing.

What do you think about the fact that with Kotlin, you don’t state the exception in the function, this is unlike Java or Swift, which both require it. Which approach do you prefer?

I think for each platform there are different methods of working. Both approaches are fine with me. I think the Kotlin approach for Android, or for Kotlin in general, gives the developer more responsibility as to what can go wrong. You need to better understand the code and the reasons behind what can go wrong with exceptions when working with Kotlin. You can solve it using annotations and documentation, but in general people need to understand that if something can go wrong it will. They need to understand then how to solve it within the runtime code that they are writing, or building. If you are using an API, then API documentation will provide you with a bit more knowledge as to what is happening under the surface, and in terms of architecture, yes you need to know that when using an API function call or whichever function you are using within your own classes, you still need to interact with them properly, so it drives you to write better code handling for all exceptions.

Do you feel the fragmentation of devices or versions in Android is a real difficulty?

Yes, we see different behaviors across devices and different versions, and making sure that the app runs smoothly across all platforms can be a bit rough. But even so, it is a lot better than what we had in the past. I hope that as we progress in time, more and more devices will be upgraded to use an API level that is safer to use, and will mitigate fragmentation. Right now, some of the features that we are building, for example API 24 and above, have major progress in comparison to API 21 and above.

As a final question, which feature would you dream that Android or Kotlin would have?

I never thought of that, because, a week ago I would say camera issues on Android. But a month ago I would say, running computer vision in AI on Android on different devices. Camera issues are due to different hardwares. Google is doing a relatively good job in trying to enforce a certain level of compliance and testing on all devices. You have quite a few tests the device has to pass both in hardware and in API. But we still see many devices attempt to bypass, or give false results to the tests.

I would say giving us support for actual devices as far back as five to seven years, instead of three, and giving an all around better camera experience over all devices.

Thank you very much to Yossi Elkrief for your time and expertise!


Shipbook gives you the power to remotely gather, search and analyze your user logs and exceptions in the cloud, on a per-user & session basis.


  1. If you have a body, you are an athlete.