State - How do we deal with it?

Me 🕶️

Mobile Enthusiastic 📱🍭

💖 Kotlin & Swift + Open source software

Works at Mercari (Merpay division) in Tokyo 🗼

github.com/kittinunf

@kittinunf

State - what is it?

State = Abstraction

Programming is all about abstraction

Programming uses language to solve problems

Example

  var x: Int? = 10 //variable is state

  
  x = 15 //update state


  x++ //mutate state


  x = null //"clean up" state
  

More example

  //somewhere in your project

  data class JobCategory(
    val id: String,
    val title: String,
    val subs: List<JobSubcategory>
  )
  

  data class JobSubcategory(
    val id: String,
    val title: String,
    val coverUrl: String
  )
 

State is EASY 🍌

Using state, just assign/update value

One knows how to use it

But .... easy  simple

State is COMPLEX 🙄 

Unpredictable ...

Race conditions ...

Hard to write tests ...

Familiar or approachable

Easy

=

Fewer concepts / not complex to understand

Simple

=

More example

More on this!

  
  // in your class, you have this variable
  var isHeaderAdded: Boolean = false
  

  // in your class, you have this sort of function
  fun updateItemCount(count: Int) {
    
  }
 
  // inside updateItemCount
  fun updateItemCount(count: Int) {
    if (isHeaderAdded) {
      val header = getHeaderView()

      if (itemCount == 0) {
        removeHeader(header)
        isHeaderAdded = false
      } else {
        header.updateItemCount(itemCount)
      }
    } else if (itemCount > 0) {
      
      val header by lazy { HeaderView() }

      header.updateItemCount(itemCount)
      addHeader(header)
      isHeaderAdded = true
    }
  }

This kinda work™

Brittle, it looks !@#$%^&* but hard to maintain 🙀

Hidden context! - header is added or not 🔬

Hard to test, no resistant to change 👴 

So, solution💡

Type = proof = prevent errors

Programmer tends to make errors

We need something to restrict what you can do

Solution💡

Type gives you the hidden context

Type Proves
ByteArray Nothing much
String String - list of Chars, not just random bytes
URL String reprsents URL, not just random list of Chars

Solution💡

interface List<out E> {
  abstract val size: Int

  abstract operator fun contains(element: E):
    Boolean

  abstract fun containsAll(elements: Collection<E>): 
    Boolean

  abstract operator fun get(index: Int): E

  abstract fun indexOf(element: E): Int

  abstract fun isEmpty(): Boolean
  
  abstract operator fun iterator(): Iterator<E>

  abstract fun lastIndexOf(element: E): Int

  abstract fun listIterator(): ListIterator<E>

  abstract fun listIterator(index: Int): 
    ListIterator<E>

  abstract fun subList(fromIndex: Int, toIndex: Int): 
    List<E>
}
interface MutableList<E> : List<E> {
  fun add(element: E): Boolean

  fun add(index: Int, element: E): Unit

  fun addAll(index: Int, elements: Collection<E>):
    Boolean

  fun addAll(elements: Collection<E>): Boolean

  fun clear(): Unit

  fun listIterator(): MutableListIterator<E>

  fun listIterator(index: Int): MutableListIterator<E>

  fun remove(element: E): Boolean

  fun removeAll(elements: Collection<E>): Boolean

  fun removeAt(index: Int): E

  fun retainAll(elements: Collection<E>): 
    Boolean

  operator fun set(index: Int, element: E): E

  fun subList(fromIndex: Int, toIndex: Int): MutableList<E>
}

List<E> is more restricted than Mutable<E>

List<E> is prevents you to expose to more errors comparing to Mutable<E>

Solution💡

  
  // in your class, you have this variable
  var isHeaderAdded: Boolean = false
  

  // in your class, you have this sort of function
  fun updateItemCount(count: Int) {
    
  }
 
  
  sealed class Content<ViewType, T> {
    
    data Empty(val empty: ViewType) : Content()
    data Value(list: List<T>, val header: ViewType) : Content()
  }
 

  // usage
  when (content) {
    is Content.Empty -> {
      addHeaderView(content.empty) // smart cast :hooray:
    }
    
    is Content.Value -> {
      // update your view with value
    }
  }

Validated<T, E>

  
 sealed class Validated<T, E> {

   class Valid(value: T)
     : Validated()
   class Invalid(errors: List<Exception>)
     : Validated()

   companion object {
     fun error(e: Exception) = 
       Invalid(listOf(e))
   }

   var isValid 
     get() = when(this) {
       is Valid -> true
       is Invalid -> false
     }
 }
 
 sealed class InputError : Throwable() {
    data class WrongFormat(reason: String) : EmailError()
    object Length : EmailError()
 }

 fun validateEmail(email: String): 
   Validated<Email, EmailError> =
   if (email.contains("@")) 
     Validated.Valid(email) 
   else error(InputError.WrongFormat("🙅‍♂️"))

 fun validateName(name: String): 
   Validated<Name, Exception> = 
   if (name.isEmpty().not()) 
     Validated.Valid(name)
   else error(InputError.Length)

RemoteData<T, E>

Represents a state of data that will be coming in the future

Eventually be either success or failure 

Captures Loading state as well

RemoteData<T, E>

Not Asked

Loading

Success(value: T)

Failure(error: E)

Summary

"Type" is your best friend, use them A LOT

Kotlin sealed class is one of them most underrated feature

Don't overuse them, understand and use where it makes sense

Q & A 🙋

State - How do we deal with it?

By Kittinun Vantasin

State - How do we deal with it?

  • 915