The 500 DON'Ts about Android Development
Tags: android, programming, kotlin
Published: 2023-03-21 23:55:20

**FYI There are no 500 DON'ts in here, just random 8 things I was thinking about. The 500 is a reference to this [Simpsons](https://www.youtube.com/watch?v=NRvaR4fqrYY) episode.**

I decided to do a small post about some of the most common mistakes I have seen during my career as an Android engineer.

You will see a little bit of everything here, Kotlin, Android SDK, commonly used third-party libraries, etc. 😉

### 1️⃣ .let isn't a replacement for if != null

I have seen many engineers misinterpret what the `.let` function is for and use it like:

```
data class EspressoMachine(
    val amountCoffee: Int,
    val hasWater: Boolean
)

fun testFoo(espressoMachine: EspressoMachine?) {
    var canPrepEspresso = false
    espressoMachine?.let {
        canPrepEspresso = it.hasWater && it.amountCoffee > 0
    }
    if (canPrepEspresso) {
        // Do something else
    }
}
```

The `let` scoping function or the `let` operator (call it whatever you want, potato-poteto) it _lets_ you run an algorithm on a variable, it is designed to transform a variable into something else.

The correct usage would be something like

```
fun testFoo(espressoMachine: EspressoMachine?) {
    val canPrepEspresso = espressoMachine?.let { it.hasWater && it.amountCoffee > 0 } 
}
```

Simple as that, again, `let` isn't a replacement for the `if` statement. Also, please do not end up in these kind of situations

```
fun testFoo() {
    foo?.let {
        boo?.let {
            // Just go with a if (foo != null && boo != null)
        }
    }
}
```

So Donnie, please don't abuse the `.let`

None of the scoped-functions are meant to be a replacement for if != null

The same thing applies to _any_ other scoped function, `apply`, `run`, `let`, etc.

For example, the `apply` lets you _apply_ changes upon an object. For example, if you have something like this 👇 

```
fun testApply(): Intent {
    val someIntent = Intent()
    someIntent.putExtra("foo", 123)
    return someIntent
}
```

You can easily change it to something a bit more readable, like:

```
fun testApply() = Intent().apply {
    putExtra("foo", 123)
}
```

So Donnie, please don't go over the top with the scoped functions and the elvis operator.

### 2️⃣ Using RecyclerView instead of LinearLayout

I often see engineers try to create a dashboard-like UI using a `RecyclerView`, where each item is a "section" of the dashboard. This does not scale well and it is not the use case for the `RecyclerView`. It is far easier, and more scalable to just have a `LinearLayout` with a bunch of `Fragments` 👇 

```
  <LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <fragment
      android:name="com.something.company.SectionOneFragment"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"/>

    <fragment
      android:name="com.something.company.SectionTwoFragment"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"/>

    <fragment
      android:name="com.something.company.SectionThreeFragment"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"/>
    
  </LinearLayout>
```

If you try to implement this using a `RecyclerView`, you will have to worry about:

- The recycling of the `ViewHolders`. If one of these "sections" contains an `EditText` you will be forced to keep track of that value in the `Adapter`.

- The sheer amount of callbacks to communicate back to the UI.

So Donnie, if every item in the `Adapter` is going to be different, don't go with a `RecyclerView` use a `LinearLayout` instead.

### 3️⃣ Mutable and immutable data

I have seen this with people who are moving away from Java. In Kotlin you have two types of variables, read-only variables  (`val`), and re-assignable variables (`var`). You also have mutable (for example `ArrayList<String>`) data, and immutable data (for example `Array<String>`) data.

Let's try to picture a `RecyclerView.Adapter<VH>` and think about the data structure we are going to use to model the list.

If we go with a read-only mutable data structure, we can update the items one way only 👇 

```
class MyAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    
    private val myList = mutableListOf<String>()
    
    fun updateList(newList: List<String>) {
        myList.apply { 
            clear()
            addAll(newList)
        }
        notifyDataSetChanged()
    }
    // ...
```

If we go with a re-assignable variable and immutable data, there's also only one way to update the list 👇 

```
class MyAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private var myList = listOf<String>()

    fun updateList(newList: List<String>) {
        myList = newList
        notifyDataSetChanged()
    }
```

But, if we go with both, a `var` variable and also use mutable data, it kinda defeats the purpose 👇 

```
class MyAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private var myList = mutableListOf<String>()

    fun updateList(newList: ArrayList<String>) {
        myList = newList
        notifyDataSetChanged()
    }
```

And also notice that now I'm tied to use `ArrayList`🫠

So Donnie, please use either `val` with mutable data or `var` with immutable data.

### 4️⃣ Too much `lateinit var`

I find that way too many times engineers rely a bit too much on the `lateinit` operator, it doesn't always guarantee that your variable will be initialized, it is just re-introducing `NullPointerException` as an `UninitializedPropertyAccessException` exception. I find it much more safe to go with either using `lazy` initialization or just initializing the `var` variable with something. For example 👇 

```
private lateinit var myRecyclerViewAdapter: MyAdapter

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // ...
    myRecyclerViewAdapter = MyAdapter()
    myRecyclerView.adapter = myRecyclerViewAdapter
}
```

Instead you could

```
private val myRecyclerViewAdapter by lazy {
    MyAdapter()
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // ...
    myRecyclerView.adapter = myRecyclerViewAdapter
}
```

### 5️⃣ Checking if context != null

It might come as a surprise to some, but there is a whole `requiresSomething()` API out there for `Fragments` and `Activities` so you don't have to null-check things that are late initialized, such as the context. For example:

If you are doing something like

```
class TestFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        someButton.setOnClickListener { 
            if (context != null) {
                startActivity(Intent(context, SomeActivity::class.java))
            }
        }
    }
}
```

You can instead do something like

```
class TestFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        someButton.setOnClickListener {
            startActivity(Intent(requireContext(), SomeActivity::class.java))
        }
    }
}
```

Like this you have the `requireActivity()` to get the attached `Activity` or the `requireView()` to get the root view, quite useful to prompt `Snackbars`. Do keep in mind that if the context hasn't been initialized, you will get an exception.

So Donnie, no need to null-check everything here.

### 6️⃣ Observables, Callbacks, and where to use them

There are two widely-use mechanisms to communicate data between components, Callback interfaces and Observables:

#### Callback 

It might be a callback interface like this 👇 

```
data class EspressoMachine(
    val callback: EspressoMachine.Callback
) {
    interface Callback {
        fun onCoffeeReady(isTooHot: Boolean)
    }
}

fun testCallback() {
    val espressoMachine = EspressoMachine(object : EspressoMachine.Callback {
        override fun onCoffeeReady(isTooHot: Boolean) {
            // Do something when ready
        }
    })
}
```

or you might use high-order functions, like this 👇 

```
data class EspressoMachine(
    private var callback: ((Boolean) -> Unit)? = null
) {
    interface Callback {
        fun onCoffeeReady(isTooHot: Boolean)
    }

    fun setOnCoffeeReady(func: ((Boolean) -> Unit)) {
        callback = func
    }
}

fun testCallback() {
    val espressoMachine = EspressoMachine()
    espressoMachine.setOnCoffeeReady {
        // Do something
    }
}
```

This is the kind of API that we see in the Android SDK. For example, when you want to set an onclick event 👇 

```
fun testCallback(view: View) {
    // () -> Unit
    view.setOnClickListener { 
        
    }
    // (View, MotionEvent) -> Unit
    view.setOnTouchListener { v, event -> 
        true
    }
    // (View, Boolean) -> Unit
    view.setOnFocusChangeListener { v, hasFocus -> 
        
    }
}
```

So let me ask you something; it is a good practice to keep code homogeneous, right? If you approach a code base where they are using `ViewBinding` you are not going to start using `Butterknife` or `Kotlin Synthetics`, right? – Okay, following that same way of thinking, if you create a custom view, you should keep things _homogeneous_ with the Android SDK, right? That's the idea here.

#### Observables 

So we have a quite a few "observables" out there, `RxJava`, `Kotlin Flow`, `LiveData`, `MutableLiveData`, the `SingleLiveEvent` ugly cousin, the (we-got-it-right-this-time) `StateFlow` and `ShareFlow` – all these observables address different use cases, some people might argue that you could use them all in the same project, if you use them to address specific use cases, and some people might tell you to just pick one. I'm kinda in the middle. I differentiate between two different use cases:

**1# Use Case: ViewModel <> UI comms** 

You want to communicate data from your `ViewModels` back to your `Activity`, `Fragment` or `Composable`, right? You have a few options here:

- LiveData / MutableLiveData / SingleLiveEvent: The "old" Android solution, I don't see anything bad about going with this option, but keep in mind that you will have to deal with the SingleLiveEvent issue, a swept-under-the-rug Google solution.

- StateFlow / ShareFlow: It would be the solution that Google recommends nowadays, and it addresses the SingleLiveEvent issue AFAIK. 

**2# Use Case: Exposing an API for UseCases / Interactors / Repositories** 

However you structure your app, I'd like to think that we should not do things like querying the backend API or fetching things from the DB, straight in the ViewModel. Usually, most engineers, create a middle layer, call them Repositories, UseCases, Interactors, whatever you want – basically a wrapper to fetch or submit data. What comes next is to figure out what kind of API these components are going to expose. We have a few solutions out there:

- RxJava: You have rx.Observable or rx.Single depending on if your stream is going to emit one (a GET request, for example) or many (actively listening for DB changes) objects. It has a `.zip`, `.contact`, `.merge` API so you can combine streams any way you want. Quite flexible – I did use it a lot before moving to Kotlin Coroutines and Flows.

- Kotlin Flow: You have `Flow` for when your stream is emitting multiple items, and you can use simple `suspendable` functions for when you expect a one-time response.

This is a very-brief and utterly-simplified version of the kind of data bus solutions we have out there, and I haven't talked about Kotlin Channles and what's the use case for that. Hopefully by now you can dig that there are _two_ types of "observables" the ones used to communicate data from the `ViewModels` back to the UI, and the one used to expose data from the `Repositories` / `UseCases`/ `Interactors` out to the `ViewModels`

So, Donnie please –

- Don't use `Callback` interfaces in `ViewModels`.
- Don't use `LiveData` as callbacks for custom views.
- Don't use `RxJava` observables to expose data from the `ViewModels`

### 7️⃣ Re-using UI

The ways in which you can re-use UI on Android are: `Fragments`, Custom Views, the `<includes>` tag, and now also 
 `Composables`. Each of these has a different use case.

- If you want to re-use a completely static piece of UI, then you can go with the `<includes>` tag. For example, a banner with hardcoded text, or a `Toolbar` that's the same everywhere. 

- If you want to re-use a piece of UI that has functionality but the functionality is independent of your business, then you can go with a Custom View. For example, a carousel of `ImageViews`, anything that makes you think "oh, I could push this up to GitHub as a lib" can probably be done as a Custom View.

- If you want to re-use a piece of UI that has functionality and that functionality is closely related to your business, then go with a `Fragment`. For example, you prompt a screen during onboarding so the user can invite people to download your app, and you also want to prompt the same screen within the app.

So Donnie, don't re use UI using `ViewHolders` or anything else.

### 8️⃣ Incorrect usage of LiveData + Coroutines

I have seen some people do things like:

```
sealed class State {
  data class Loading(
    val isLoading: Boolean
  ): State()
  data class DataSuccessfullyFetched(
    val someData: Data
  ): State()
  object ErrorFetchingData : State()
}

val state = MutableLiveData<State>() // Or SingleLiveEvent 

viewModelScope.launch(Dispatcher.IO) {
   state.postValue(State.Loading(true))
   val response = someRepository.fetchData()
   if (response != null) {
     state.postValue(State.DataSuccessfullyFetched(response))
   } else {
      state.postValue(State.ErrorFetchingData)
   }
   state.postValue(State.Loading(false))
}
```

We want the `state` property to go through the `State.Loading(true)` state, then either `State.DataSuccessfullyFetched(response)` or `State.ErrorFetchingData` and finally `State.Loading(false)`, right?

Well, what's going to happen is that the `state` property is just going to "broadcast" the last set value, `State.Loading(false)`.

The issue here is that you shouldn't be using `postValue` – "but if I use `setValue` instead I get a background thread error message", that's correct, because you shouldn't be using the `Dispatcher.IO` at all, the only thing that needs to run on a "background thread" is the `fetchData()` suspendable function, which you created as:

```
suspendable fun fetchData() = withContext(Dispatcher.IO) {
  ...
}
```

What we should actually be doing is 👇 

```
...
viewModelScope.launch {
   state.value = State.Loading(true)
   val response = someRepository.fetchData()
   if (response != null) {
     state.value = State.DataSuccessfullyFetched(response)
   } else {
      state.value = State.ErrorFetchingData
   }
   state.value = State.Loading(false)
}
```

So Donnie, using `Dispatcher.IO` doesn't make things magically run in the background thread, also just adding the `suspendable` keyword to a function doesn't make that function run in the background either. There is no reason why we should use the `postValue` in the `ViewModel`

-------------------------------

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rhwwp2aj0ft9elw1m8vu.png)

Sorry, I lied, there are no 500 don'ts 😜 – Many of the things I pointed out here are up to personal criteria, you can use a knife as a fork if you want, you would probably still be able to pick up the food, no one can stop you. You might also want to use your underwear as a hat – and some people might even think you are cool! – but I'm not sure people will feel totally comfortable around you 🤷.

Anywho, hope you liked it! 😉