Skip to main content

9 posts tagged with "android"

View All Tags

· 13 min read
Petros Efthymiou

Biometric Authentication in Android

Introduction

In today's digital landscape, security and user experience are paramount considerations for developers creating Android applications. Biometric authentication, a revolutionary advancement in mobile security, has emerged as a pivotal solution that addresses both security concerns and user convenience. With the rise of data breaches and the increasing dependency on mobile devices for various transactions, implementing robust authentication mechanisms is non-negotiable.

Biometric authentication is a cutting-edge method that leverages the unique physiological and behavioral characteristics of an individual to grant access to applications and sensitive data. Instead of relying solely on traditional methods like PINs or passwords, biometric authentication harnesses distinctive traits such as fingerprints, facial features, and iris patterns to verify a user's identity.

Advantages of Biometric Authentication

  1. Enhanced Security: Biometric authentication offers a higher level of security compared to traditional methods. Unlike passwords or PINs, which can be forgotten, shared, or hacked, biometric characteristics are unique to each individual. Having said that, there are security gaps in biometrics as well, such as authentication false positives due to poor device hardware, but those can be mitigated, as we will see later. Another opportunity to bypass it might be malicious fingerprint capturing (with photos or other methods) for user imitation.

  2. User Convenience: One of the standout benefits of biometric authentication is its ease of use. Users no longer need to remember complex passwords or worry about typing errors. A simple touch of a finger or a glance at the camera is all it takes to gain access. This frictionless experience not only reduces user frustration but also encourages secure behavior.

  3. Seamless Interaction: Biometric authentication seamlessly integrates into the user's natural interaction with the device. It eliminates the need to switch between apps to retrieve passwords or codes, streamlining the user journey and increasing overall efficiency.

  4. Reduced Friction: Traditional authentication methods often lead to abandoned sign-up or login processes due to the cumbersome nature of password entry. Biometric authentication reduces this friction, leading to higher user engagement and retention rates.

  5. Multifactor Authentication: Many modern devices support multifactor authentication, combining biometric traits with other factors such as PINs or tokens. This layered approach further enhances security by adding an extra barrier against unauthorized access.

In this step-by-step guide, we will explore how to implement biometric authentication in Android applications using the power of Jetpack Compose. To read more about Jetpack Compose you may visit our article. By combining the capabilities of Jetpack Compose with the Android Biometric API, developers can craft applications that prioritize security and provide a seamless and delightful user experience.

In the following sections, we will walk through the process of integrating biometric authentication into an Android app using Jetpack Compose. We will cover various aspects such as understanding the Biometric API, preparing the project, implementing different biometric modalities, and ensuring security best practices.

Stay tuned as we embark on this journey to create more secure, user-centric, and innovative Android applications with the power of biometric authentication and Jetpack Compose.

Understanding Biometric Authentication

Android devices offer several biometric modalities, each with its own set of characteristics and advantages.

Fingerprint Authentication:

Fingerprint authentication is one of the most widely recognized biometric methods. It relies on capturing and analyzing the distinctive patterns in a user's fingerprints. As every individual has unique ridge patterns and minutiae points at their fingertips, fingerprint authentication offers a high level of accuracy and security. Android devices equipped with fingerprint sensors enable users to unlock their devices, authorize transactions, and access sensitive apps simply by placing their registered finger on the sensor. This method has gained significant popularity due to its ease of use and quick recognition.

Face Recognition:

Face recognition involves capturing and analyzing a user's facial features to establish identity. It works by detecting key facial landmarks and comparing them with registered data. The minimum hardware requirement is a high-resolution camera. This camera should have sufficient resolution and quality to detect facial features accurately. To enhance security, some phones carry depth sensors that create a 3D depth map of the user's face. Or, even better, an infrared camera that enables IRIS recognition. Just with a front-facing camera, the device is considered to have weak biometric authentication.

Face recognition is convenient and non-intrusive, providing a seamless user experience. However, it's important to note that lighting conditions and angle variations can impact its accuracy.

Iris Recognition:

Iris recognition is a highly secure biometric method that involves capturing and analyzing the unique patterns in a user's iris, which is the colored part of the eye surrounding the pupil. Like fingerprints, iris patterns are distinct to each individual and remain stable over time. This method offers a higher degree of accuracy and security due to the complexity of the iris patterns. While iris recognition may require specific hardware, it provides a robust solution for applications that demand stringent security measures.

The Role of Biometric Authentication in App Security:

Biometric authentication plays a crucial role in enhancing the security of sensitive app functionalities. While traditional authentication methods like passwords can be compromised through hacking, phishing, or even user negligence, biometric traits are inherent and difficult to replicate. By incorporating biometric authentication as an additional security layer, apps can ensure that only authorized individuals gain access to critical features, sensitive data, and financial transactions.

For instance, financial apps can use biometric authentication to authorize high-value transactions, ensuring that even if a user's device is stolen, unauthorized transactions cannot be carried out without the user's biometric input. Similarly, healthcare apps can use biometrics to secure patient records and medical data, safeguarding sensitive information from unauthorized access.

The significance of biometric authentication extends beyond security. By reducing the need for complex passwords and PINs, biometrics offer a seamless and user-friendly experience, contributing to higher user engagement and satisfaction. Users are more likely to adopt apps that prioritize both security and convenience.

As we proceed through this step-by-step guide, we will explore how to harness the power of Jetpack Compose to integrate biometric authentication seamlessly into your Android apps. By combining the strength of biometric modalities with the modern UI capabilities of Jetpack Compose, you'll be able to create applications that are not only secure but also delightful to use. Stay with us as we dive deeper into the implementation details and unlock the potential of biometric authentication in your Android projects.

Prerequisites

Before diving into the implementation of biometric authentication in your Android app using Jetpack Compose, there are several prerequisites that you need to ensure are in place. These prerequisites ensure that your app can effectively utilize the Biometric API and provide a seamless and secure user experience.

Minimum SDK Version:

To implement biometric authentication, your app should have a minimum SDK version of 23 (Android 6.0, Marshmallow) or higher, as the Biometric API was introduced in this version.

Hardware Requirements:

The availability of biometric authentication methods depends on the hardware capabilities of the user's device. Such as:

  • Fingerprint sensor for fingerprint authentication.
  • Front-facing Camera for facial recognition.
  • Infrared camera for iris recognition.

Ensure that your app gracefully handles scenarios where the required hardware is not available on the device.

Setting Up Biometric Authentication and Jetpack Compose

Now that we've covered the prerequisites, it's time to set up your Android project for biometric authentication using the Android Biometric API and Jetpack Compose. This section will guide you through adding the necessary permissions and dependencies to your project, ensuring that you're well-equipped to integrate biometric authentication seamlessly into your app.

  1. Adding Permissions:

Depending on the biometric modality you plan to use, you may need to add specific permissions to your app's AndroidManifest.xml file. For example, if you intend to use face recognition, you must request CAMERA permission to access the front-facing camera:

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

Make sure to request permissions at runtime if your app targets Android 6.0 (Marshmallow) or higher. You can use the AndroidX Activity or Fragment libraries to handle permission requests effectively.

  1. Adding Dependencies:

To begin implementing biometric authentication using the Android Biometric API and Jetpack Compose, you must add the required dependencies to your app's build.gradle file. We'll be using the Biometric API to interact with biometric hardware and the Jetpack Compose libraries for UI creation.

In your app's build.gradle file, add the following dependencies:

android {
// ...
buildFeatures {
compose true
}

composeOptions {
kotlinCompilerExtensionVersion "1.5.1"
}
}

dependencies {
// ...
implementation "androidx.compose.ui:ui:1.4.3"
implementation "androidx.compose.material:material:1.4.3" // Check for the latest version
implementation "androidx.activity:activity-compose:1.7.2"
implementation("androidx.biometric:biometric:1.2.0-alpha05")

}

The androidx.compose and androidx.activity:activity-compose are required for building the user interface using Jetpack Compose.

The androidx.biometric:biometric dependency provides access to the Android Biometric API, which is essential for implementing biometric authentication.

Checking Biometric Device Compatibility

Now, let’s start implementing the actual solution. As we are using Jetpack Compose we will create a MainActivity and add our Composables to it.

class MainActivity : AppCompatActivity() {

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

We now need to implement the BiometricAuthenticationScreen Composable that will be responsible for the actual biometric authentication.

@Composable
fun BiometricAuthenticationScreen() {
val context = LocalContext.current as FragmentActivity
val biometricManager = BiometricManager.from(context)
val canAuthenticateWithBiometrics = when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
BiometricManager.BIOMETRIC_SUCCESS -> true
else -> {
Log.e("TAG", "Device does not support strong biometric authentication")
false
}
}

Surface(color = MaterialTheme.colors.background) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
if (canAuthenticateWithBiometrics) {
//TODO perform biometric authentication
} else {
Text(text = "Biometric authentication is not available on this device.")
}
}
}
}

We have implemented a simple Composable that first of all is using the BiometricManager to identify whether biometric authentication is available on this device. It stores the result on a boolean value. As we explained earlier there are devices, particularly older devices, that do not support any fingerprint, face, or iris authentication.

In our implementation are logging those cases and presenting a text on the screen that informs the user. In a real-world app, we would probably want to redirect the user to a username-password authentication screen instead.

Implementing Biometric Authentication

Let’s proceed with implementing the biometric authentication. First of all, we will create a button Composable that will appear on the screen when the device supports biometric authentication.

@Composable
fun BiometricButton(
onClick: () -> Unit,
text: String
) {
Button(
onClick = onClick,
modifier = Modifier.padding(8.dp)
) {
Text(text = text)
}
}

Now we will implement the authenticate with biometric function.

fun authenticateWithBiometric(context: FragmentActivity) {
val executor = context.mainExecutor
val biometricPrompt = BiometricPrompt(
context,
executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
//TODO handle authentication success, proceed to HomeScreen
Log.d("TAG", "Authentication successful!!!")
}

override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
Log.e("TAG", "onAuthenticationError")
//TODO Handle authentication errors.
}

override fun onAuthenticationFailed() {
Log.e("TAG", "onAuthenticationFailed")
//TODO Handle authentication failures.
}
})

val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Biometric Authentication")
.setDescription("Place your finger the sensor or look at the front camera to authenticate.")
.setNegativeButtonText("Cancel")
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.build()

biometricPrompt.authenticate(promptInfo)
}

Initially, we are creating a Biometric Prompt with the respective callbacks that decide what happens on each occasion. The onAuthenticationSucceedded callback is called when the authentication is successful, here you probably want to include an Intent for your HomeActivity or present your HomeScreen Composable. Personally, I prefer to separate the pre-authentication app from the post-authentication app with a separate activity.

After we create the BiometricPrompt we also create the PromptInfo that defines the options and literals that will present to the user when he triggers the biometric authentication.

Then we define what authenticators we want to allow. Here we are requesting the BIOMETRIC_STRONG type of authentication. This includes:

  1. Fingerprint authentication.
  2. Face recognition with IRIS detection.
  3. Face recognition with a 3D depth sensor.

As we mentioned earlier, a device that only carries a front-facing camera cannot apply strong biometric authentication. The OS is picking automatically the strong authentication option that is available on the current device (fingerprint or face recognition). Usually, devices don’t carry more than 1 strong-biometric authentication sensors, as this would unnecessarily increase their cost.

Finally, we call the authenticate function on the biometricPrompt to trigger the actual authentication popup.

In order to finalize the implementation, we need to display the BiometricButton on the devices that support biometric authentication. Replace the //TODO perform biometric authentication with BiometricButton(...)

    Surface(color = MaterialTheme.colors.background) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
if (canAuthenticateWithBiometrics) {
BiometricButton(
onClick = {
authenticateWithBiometric(context)
},
text = "Authenticate with Biometric"
)
} else {
Text(text = "Biometric authentication is not available on this device.")
}
}
}

The implementation is completed! You can now build and install the app on a device that supports biometrics and perform the authentication!

Biometric Authentication Error Handling

Let’s now discuss the error handling of biometric authentication.

Both onAuthenticationError and onAuthenticationFailed are callback methods of the BiometricPrompt.AuthenticationCallback class. These methods are invoked based on different scenarios during the biometric authentication process.

onAuthenticationError Method:

The onAuthenticationError method is called when an error occurs during the biometric authentication process. This could include various types of errors, such as the user clicking the cancel button, sensor errors, hardware issues, or other unexpected conditions that prevent successful authentication. The method receives two parameters:

  1. errorCode: An integer code representing the specific error that occurred. This code can be used to identify the nature of the error.
  2. errString: A human-readable error message that provides additional details about the error.

onAuthenticationFailed Method:

The onAuthenticationFailed method is called when the biometric authentication process fails to recognize the biometric data provided by the user. This can occur when the biometric data presented to the sensor does not match any enrolled biometric template. It's important to note that this callback is not invoked for every unsuccessful attempt; it's specifically for cases where the biometric data provided cannot be matched to any registered data.

Similar to the onAuthenticationError method, the onAuthenticationFailed method should be used to handle authentication failures by implementing appropriate logic.

In summary, onAuthenticationError is called when there's an error during the authentication process, and onAuthenticationFailed is called when the provided biometric data cannot be matched to any registered data. Both methods are essential for creating a comprehensive biometric authentication experience that informs users about errors and failures and guides them through the authentication process.

Conclusion

As we conclude this step-by-step guide on implementing biometric authentication in Android with Jetpack Compose, we've explored the fusion of cutting-edge security measures and user-centric design principles. Biometric authentication has emerged as a formidable solution that not only enhances the security of your Android applications but also elevates the user experience to new heights.

By harnessing the power of biometric modalities such as fingerprint, face recognition, and iris authentication, developers can provide users with a seamless and secure way to access sensitive features, authenticate transactions, and interact with confidential data. The integration of Jetpack Compose further amplifies the potential, enabling the creation of intuitive and visually appealing user interfaces that align with modern design trends.

Shipbook provides awesome remote logging capabilities that can help you identify, debug, and fix critical authentication errors at the time they appear!

Thank you for joining us on this exploration of biometric authentication with Jetpack Compose. As technology continues to evolve, we encourage you to stay curious, experiment, and continually enhance your skills to build exceptional and secure experiences for Android users worldwide.

· 9 min read

RecyclerView Vs ListView Introduction

RecyclerView and ListView are two popular options for displaying long lists of data within an Android application. Both are subclasses of the ViewGroup class and can be used to display scrollable lists. However, they have different features, capabilities and implementation.

The process of implementing both may seem pretty similar, for example,

  • You get list of data
  • You create an adapter
  • Find the view to which you have to display the list
  • Set the adapter to that list

ListView was one of the earliest components introduced in Android development for displaying a scrollable list of items. Although it provided basic functionality and ease of implementation, it had its limitations, especially when it came to handling large data sets and customizing the appearance and behavior of the list.

As Android applications evolved and the need for more sophisticated list management became apparent, RecyclerView was introduced as a more versatile and efficient solution for displaying lists. As a developer, it's essential to understand the key differences between ListView and RecyclerView to appreciate their respective advantages and disadvantages.

In this article, we'll explore the key differences between RecyclerView and ListView and give you a good understanding of when to use what and how and also appreciate why RecyclerView came into existence over ListView.

ListView

ListView was introduced in Android 1.0 and has been around since then. ListView was the go-to solution for displaying lists of data before RecyclerView was introduced.

One of the biggest advantages of using a ListView is that it's simpler to implement, easier to use. Here is an example of how simply ListViews can be implemented in Android.

main activity

Link to snippet

As you can see, the code is pretty simple and straight-forward compared to the RecyclerView implementation one has to do by implementing custom adapter and viewholder classes.

If you ask any Android Developer about the difference between the two they would say something like “ListView is still available and can be a good solution for displaying smaller lists of data. But, as the complexity of the app increases, the ListView might not be the best solution for managing and displaying large amounts of data.” Let’s try to understand why?

To implement anything a little more complex than just a simple list of Strings, it’s a good practice to write our own Adapter class whose responsibility is to map the data to the positioned view as we scroll through the list.

Let’s write our own adapter class instead of a simple ArrayAdapter for the above snippet.

list adapter

Link to snippet

The getView function on a high level does the following:

  • Gets each view item from the listview
  • Find references to its child views
  • Sets the correct data to those views depending upon the position
  • Returns the created view item.

For each row item in the 1000 item list, we don’t have to create 1000 different views, we can repopulate and reuse the same set of views with different data depending on the position in the list. This can be a major performance boost as we are saving tons of memory for a large list. This is called View-Recycling and is a major building block for RecyclerView which we will see in a while and here is a representation of how View Recycling works.

graph

Now, we have recycled the views by a simple null check and saved memory but if we look inside the getView() function we can see that we are trying to find the references to the child views by doing findViewByID() calls.

Depending upon how many child views there are, in my example code there are 4 so for each item in the list, we are calling findViewByID() 4 times.

Hence for a 1000 item list, there will be 4000 findViewByID() calls even though we have optimized the way in which the rowItem views are initialized. To help fix this problem for large lists, the ViewHolder pattern comes into play.

ViewHolder Pattern in Android

The ViewHolder pattern was created in Android to improve the performance of ListViews (and other AdapterView subclasses) by reducing the number of calls to findViewById().

When a ListView is scrolled, new views are created as needed to display the list items that become visible. Each time a new view is created, the findViewById() method is called to find the views in the layout and create references to them. This process can be slow, especially for complex layouts with many views while also at the same time the instantiated views references are kept in memory for the whole list which can grow directly proportional to the size of the list you are rendering.

The ViewHolder pattern addresses this performance issue by caching references to the views in the layout. When a view is recycled (i.e., reused for a different list item), the ViewHolder can simply update the views with new data, rather than having to call findViewById() again.

Implementing ViewHolder Pattern in our ListView

Lets implement our ViewHolder class inside the MyListAdapter class.

MyListAdapter class

Code Snippet

With the above mentioned changes, we have created a structure to:

  • Reuse the View for each item in the list instead of creating new ones for each item in the list.
  • Reduce the number of findViewByID() calls which in case of complex layouts and large number of items in the lists can take down the performance of the app significantly.

These are the two key things which are provided as a structure to the developers with RecyclerView apart from other features of customisations in RecyclerView.

Drawbacks of Using ListView

  • Inefficient scrolling due to inefficient memory usage out of the box
  • Lesser flexibility to customize how the list items should be positioned.
  • Can only implement a vertically scrolling list.
  • Implementing animations can be hard and complex out of the box
  • Only offers notifyDataSetChanged() which is an inefficient way to handle updates.

RecyclerView

RecyclerView was introduced in Android 5.0 Lollipop as an upgrade over the ListView. It is designed to be more flexible and efficient, allowing developers to create complex layouts with minimal effort.

It uses "recycling" out of the box which we have seen above. It also has more flexible layout options, allowing you to create different types of lists with ease and also provides various methods to handle data set changes efficiently.

Let’s use RecyclerView instead of ListView in our above implementation.

RecyclerView

As you can see there are multiple functions to override instead of just one getView() function of ArrayAdapter which makes the implementation of RecyclerViews not as beginner friendly as compared to ListView. It can also feel like an overkill implementation for the simplest of the lists in Android.

Benefits of Using RecyclerView

  • The major advantage of RecyclerView is its performance. It uses a view holder pattern out of the box, which reuses views from the RecyclerView pool and prevents the need to constantly inflate or create new views. This reduces the memory consumption of displaying a long list compared to ListViews and hence improves performance.

  • With LayoutManager you can define how you want your list to be laid out, linearly, in a grid, horizontally, vertically rather than just vertically in a ListView.

  • RecyclerView also offers a lot of customisation features over listview that make it easier to work with. For example, It supports drag and drop functionality, rearrange items in the list, item swiping gestures features like deleting or archiving items in the list. Below is an attached example code on how easy it is to extend the functionality to add swiping gestures.

// Set up the RecyclerView with a LinearLayoutManager and an adapter
recyclerView.layoutManager = LinearLayoutManager(this)
adapter = ItemAdapter(createItemList())
recyclerView.adapter = adapter

// Add support for drag and drop
val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
// Set the movement flags for drag and drop and swipe-to-dismiss
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
return makeMovementFlags(dragFlags, swipeFlags)
}

override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
// Swap the items in the adapter when dragged and dropped
adapter.swapItems(viewHolder.adapterPosition, target.adapterPosition)
return true
}

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// Remove the item from the adapter when swiped to dismiss
adapter.removeItem(viewHolder.adapterPosition)
}
})

// Attach the ItemTouchHelper to the RecyclerView
itemTouchHelper.attachToRecyclerView(recyclerView)

  • Implementing animations is pretty simple in RecyclerView and can be done by simply setting the itemAnimator as shown below:
val itemAnimator: RecyclerView.ItemAnimator = DefaultItemAnimator()
recyclerView.itemAnimator = itemAnimator

Best Practices to keep in mind with RecyclerView

To ensure the best results, developers should follow best practices when working with RecyclerView and ListView. For example:

  • Use item animations sparingly, as too many animations can lead to janky performance.

  • To update the UI with a RecyclerView, we can use the notifyItemInserted(), notifyItemRemoved() or even notifyItemChanged() methods, which tells the adapter that the data has changed and the list needs to be refreshed, but if not used responsibly can lead to redundant rebuilds of the list and introduce unwanted bugs.

Conclusion

In this article, we started off with implementing a simple list using ListView and made changes to it which don’t come out of the box with ListView to make it more memory efficient, like View Recycling and View Holder pattern, only to realize the limitations of customizations available in ListView.

Then we implemented the same list with RecyclerView which enforces developers to implement the features of View Recycling and ViewHolder pattern out of the box making them efficient, customizable and performant out of the box explaining their popularity as a solution in the Android Community.

· 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!

· 8 min read
Nikita Lazarev-Zubov

ConstraintLayout

Does ConstraintLayout Replace Other Layout Options in Android?

Even though Jetpack Compose has become the recommended tool for building Android applications’ UI, the vast majority of applications still use traditional layout modes and their XML-based syntax. Android SDK provides us with many layout options. Some are already obsolete, but others remain popular and are widely used, including the newest offering: ConstraintLayout. Before we assess which options are actually effective, let’s briefly review the basics of the Android layout system.

Android Layout Basics

The fundamental building block of UI in Android is the View class, which represents a rectangular area on the screen. It’s also a base class for specific views like Button and ImageView. On top of them are ViewGroups—special Views that are used as containers for other views. ViewGroup is also the base class for various layout classes.

Android offers multiple layout options, including RelativeLayout, FrameLayout, and LinearLayout. However, back in 2018, ConstraintLayout was introduced, presumably, to rule them all. But does it live up to the hype? Let’s find out by looking at an example.

Android Layout Example

Let’s pretend ConstraintLayout doesn’t exist and build a UI for the login screen of our Layout Guru application using only pre-ConstraintLayout options.

Old Ways

Here’s what we’re going to build:

Layout Guru’s login screen

Figure 1: Layout Guru’s login screen

The view that we’re going to implement consists of two pairs of input fields and text labels centered on the screen. According to specification, each field takes up 60% of the screen width, and the text occupies the rest of the width. The application’s logo is centered above the fields, and uses 70% of the width. The “Sign In” button is positioned directly below the bottom input field and aligned to the right side of the screen.

Let’s start with one of the input text fields. The most straightforward way to implement it is with a horizontal LinearLayout. The layout_weight attribute will help us to set the desired width distribution. Here’s the layout’s XML:

    <LinearLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="1">

<TextView
android:id="@+id/emailInputTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.4"
android:text="@string/email_address"
android:textColor="@color/black" />

<EditText
android:id="@+id/emailInputField"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.6"
android:inputType="textEmailAddress"
android:autofillHints="Email"
android:hint="@string/email_address"
android:backgroundTint="@color/black" />

</LinearLayout>

The second input is similar, but uses a different inputType’s value. Both inputs can be wrapped with a vertical LinearLayout:

    <LinearLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<include layout="@layout/email_field"/>
<include layout="@layout/password_field" />

</LinearLayout>

Finally, let’s combine the input fields with the rest of UI elements in a single RelativeLayout. For the first step of this process, we can add inputs to the layout and center them:

    <include
layout="@layout/login_form"
android:id="@+id/login_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />

Then, we can add the “Sign In” button below the inputs, and align it to the right side of the screen:

    <Button
style="?android:attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_below="@+id/login_form"
android:layout_alignParentEnd="true"
android:backgroundTint="@color/white"
android:text="@string/sign_in"
android:textColor="@color/black" />

The trickiest part, though, is the logo. Putting it above the inputs is easy, but there’s no straightforward way to make it take only 70% of the width of the screen using RelativeLayout. One way to achieve this is to put the image inside another LinearLayout, which has a convenient way of manipulating its child views’ weight (but doesn’t provide a way to position elements relative to each other):

    <LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_above="@+id/login_form"
android:weightSum="1">

<ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.7"
android:src="@drawable/logo"
android:contentDescription="@string/layout_guru" />

</LinearLayout>

And here’s an outline of the resulting XML:

    <RelativeLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp">

<LinearLayout
<!--...-->>
<ImageView
<!--...-->>
</LinearLayout>

<include
<!--...-->>

<Button
<!--...-->>

</RelativeLayout>

Looking at the result, we can already draw one important conclusion: even simple pieces of UI require a lot of code and mixing-and-matching of various layout types.

ConstraintLayout

Let’s look at how the same screen could be implemented using ConstraintLayout.

This time, let’s start by putting two EditTexts and two TextViews in the center of the screen, and placing them relative to one another exactly as we did before using a combination of multiple LinearLayouts. Because the text input fields are higher than their text labels, we constrain the top one to the parent’s top, the bottom one to the parent’s bottom, and combine them into a packed chain. This will make them centered vertically as a whole. Then, the text fields can be aligned to the inputs’ baselines. This is the corresponding XML snippet:

    <TextView
android:id="@+id/emailInputTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/email_address"
android:textColor="@color/black"
app:layout_constraintBaseline_toBaselineOf="@id/emailInputField"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_percent="0.4" />

<EditText
android:id="@+id/emailInputField"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:autofillHints="Email"
android:backgroundTint="@color/black"
android:hint="@string/email_address"
android:inputType="textEmailAddress"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/passwordInputField"
app:layout_constraintStart_toEndOf="@id/emailInputTitle"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintWidth_percent="0.6" />

<TextView
android:id="@+id/passwordInputTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/password"
android:textColor="@color/black"
app:layout_constraintBaseline_toBaselineOf="@id/passwordInputField"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_percent="0.4" />

<EditText
android:id="@+id/passwordInputField"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:autofillHints="Password"
android:backgroundTint="@color/black"
android:hint="@string/password"
android:inputType="textPassword"
app:layout_constraintTop_toBottomOf="@+id/emailInputField"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/passwordInputTitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintWidth_percent="0.6" />

The rest of the work is fairly straightforward. The image can be pinned to the top of the parent and to the top of the topmost input field. The relative width can be be provided using the layout_constraintWidth_percent attribute:

    <ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:src="@drawable/logo"
android:contentDescription="@string/layout_guru"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/emailInputField"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintWidth_percent="0.7" />

Positioning of the Button is simple as well:

    <Button
style="?android:attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:backgroundTint="@color/white"
android:text="@string/sign_in"
android:textColor="@color/black"
app:layout_constraintTop_toBottomOf="@id/passwordInputField"
app:layout_constraintEnd_toEndOf="parent"/>

An outline of the resulting layout is self explanatory:

    <androidx.constraintlayout.widget.ConstraintLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp">

<ImageView
<!--...-->>

<TextView
<!--...-->>
<EditText
<!--...-->>

<TextView
<!--...-->>
<EditText
<!--...-->>

<Button
<!--...-->>

</androidx.constraintlayout.widget.ConstraintLayout>

So, coming back to the original question—does ConstraintLayout replace other layouts? No doubt one can build a complicated UI by means of ConstraintLayout alone. Although, looking at the resulting code, some might prefer traditional options as being (arguably) easier to modularize and reuse, relatively complicated UI can be built simpler and using less code. The more sophisticated the UI, the more evident the statement becomes. This only confirms the conclusion from the previous section.

Another advantage of ConstraintLayout is that it’s more straightforward when building UI by means of the visual design tools of the Android Studio instead of coding it in XML.

Before we jump to conclusions, though, let’s look at another important metric: performance.

Layout Rendering Performance

Android provides us with useful developer tools that can help to measure rendering efficiency, one of which is Profile GPU Rendering. The output of the tool for each layout implementation will look something like this:

GPU Rendering GPU Rendering ConstraintLayout

Figure 2: Profile GPU Rendering output for the two layouts, with ConstraintLayout on the right

The ConstraintLayout option, on the right, is slightly shorter on the horizontal axis, and has fewer red spikes, which translates to less CPU overhead.

Let’s also look at the output from another tool—Debug GPU Overdraw:

GPU Overdraw GPU Overdraw ConstraintLayout

Figure 3: Debug GPU Overdraw output for the two layouts, with ConstraintLayout again on the right

The results are, again, very similar, but the RelativeLyout/LinearLayout version (on the left) has more purple areas—which mean areas that were redrawn once—and even one small green area indicating two redraws.

Although the difference between two layouts appears insignificant at first glance, in real-world situations with a more complicated user interface, the penalty can easily become noticeable and result in choppy animations and visible delays. Let’s explore why that’s the case.

Double Taxation

The phenomenon of slower rendering of nested layouts is widely referred to in the Android community as double taxation. While the system renders the view hierarchy, it iterates over the elements multiple times before finalizing the size and position of each view: At the first pass, the layout system calculates each child’s position and size based on the child’s layout After that, the system makes another iteration, taking into account the layout parameters of the parent layout. The more levels of hierarchy, the bigger the overhead. The problem applies to RelativeLayout, horizontal LinearLayout, and GridLayout.

If performance problems with rendering begin to occur, one of the first things to try is eliminating nested layouts wherever possible. Another potential way to experience an improvement is to switch to ConstraintLayout, which is cheaper in terms of underlying calculation because of its “flat” nature.

Conclusion

While choosing between the newer ConstraintLayout and other, more “traditional” alternatives, several factors should be considered. First of all, it's true that ConstraintLayout can turn into a universal solution for any type of UI. Additionally, for truly complicated user interfaces, ConstraintLayout can be a more lightweight and performant solution. On the other hand, in very simple cases where LinearLayout would provide a more straightforward solution, ConstraintLayout might be overkill.

Logging

If you need to log information related to rendering, Android has an interface called ViewTreeObserver.OnDrawListener that can be easily put to use together with a system to collect and store your log messages remotely, such as Shipbook.

· 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.

· 8 min read
Uditha Maduranga

in app purchases image by mudassar iqbal from pixabay

Introduction

In-app purchases are a common way for developers to create a free application, and then provide users with options to upgrade through in-app purchases. Google Play in-app purchases are the simplest solution to selling digital products or content on Android apps. Therefore, many app developers who are looking to sell digital goods, or offer premium membership to users, use the Google Play in-app billing process for smooth and easy checkouts.

· 3 min read
Elisha Sterngold

Log Severity Intro

log severity ruler gif

This subject may sound boring. Don’t all programmers know which log severity should be used? The answer is that people are not logging their app in a systematic way. There are several guides that explain the levels but they usually just define them. I’ll try to help you decide which log level should be used. I’ll give examples so that that you will be able to copy it into your app. I’m going to list the severities of Android.