Stop refactoring. Kotlin. Android

Introduction

, , . , - , . - . , - ... , ?





, . , PullRequest-.

. .





if-else when

Java Android. Kotlin, .





fun getNumberSign(num: Int): String = if (num < 0) {
    "negative"
} else if (num > 0) {
    "positive"
} else {
    "zero"
}
      
      



- 7 . :





fun getNumberSign(num: Int): String = when {
    num < 0 -> "negative"
    num > 0 -> "positive"
    else -> "zero"
}
      
      



, 5.





 if-else



  . 2 (Kotlin + Java), . - " …"





: . , Kotlin  else-if



. . №9   .





 when



 , . Kotlin (  ) , . , .





:





1.





data class Message(
  // ...
  val isDelivered: Boolean
)
      
      



? ? ,  Message



  ? , , ?





2.





data class Message(
  // ...
  val isDelivered: Boolean,
  val isRead: Boolean
) 
      
      



, ProductOwner . ? - "" . , - . - must have IT . - , , , .





" " , . -> -> -> -> -> -> -> -> . - Boolean ?!!! COVID-19 .





? PO, . , , . , . , ?





3.





4. e.t.c.





, ,  Message



  : , , , …  . :





data class Message(
  // ...
  val state: State
) {
    enum class State {
        SENT,
        DELIVERED,
        SHOWN_IN_NOTIFICATION,
        READ
    }
}
      
      



. , , … , , , . , ? . , , . , :





data class Message(
  // ...
  val states: Set<State>
) {
  fun hasState(state: State): Boolean = states.contains(state)
}
//  
data class Message(
    // ...
    val states: States
) {
    enum class State(internal val flag: Int) {
        SENT(1),
        DELIVERED(1 shl 1),
        READ(1 shl 2),
        SHOWN_IN_NOTIFICATION(1 shl 3)
    }

    data class States internal constructor(internal val flags: Int) {
        init {
          check(flags and (flags+1)) { "Expected value: flags=2^n-1" }
        }
        constructor(vararg states: State): this(
            states.map(State::flag).reduce { acc, flag -> acc or flag }
        )

        fun hasState(state: State): Boolean = (flags and state.flag) == state.flag
    }
}
      
      



: , , .  ? ?  - - . - . - .





:





data class Message(
  //..
  val isSent: Boolean,
  val isDelivered: Boolean
  val isRead: Boolean,
  val isShownInNotification: Boolean
) 
//...
fun drawStatusIcon(message: Message) {
  when {
    message.isSent && message.isDelivered && message.isRead && message.isShownInNotification -> 
    	drawNotificationStatusIcon()
    message.isSent && message.isDelivered && message.isRead -> drawReadStatusIcon()
    message.isSent && message.isDelivered -> drawDeliviredStatusIcon()
    else -> drawSentStatus() 
  }
}
      
      



() . .





. , .





data class User(
    val username: String?
    val hasUsername: Boolean
)
      
      



. GUI . , ,  hasUsername



. , .





// OK
val user1 = User(username = null, hasUsername = false) 
// ,   
val user2 = User(username = "user", hasUsername = false) 
// OK
val user3 = User(username = "user", hasUsername = true) 
// ,    ,     
val user4 = User(username = null, hasUsername = true) 
// ,   ,     
val user5 = User(username = "", hasUsername = true) 
// ,   ,     
val user6 = User(username = " ", hasUsername = true) 
      
      



. - username



.





data class User(
    val username: String?
) {
    fun hasUsername(): Boolean = !username.isNullOrBlank()
}
      
      



- . , . , . :









data class User(
    val username: String?
) {
    val hasUsername: Boolean = !username.isNullOrBlank()
    val hasUsernameLazy: Boolean by lazy { !username.isNullOrBlank() }
}
      
      



  • .





class UsernameHelper {
    private val cache: MutableMap<User, Boolean> = WeakHashMap()
    
    fun hasUsername(user: User): Boolean = cache.getOrPut(user) { 
      !user.username.isNullOrBlank() 
    }
}
      
      



-

, . , , … . , , , .





3rd party services backend.    .





// ...
val result = remoteService.getConfig()
if (result is Result.Success) {
  val remoteConfig = result.value.clientConfig?.keys
  for (localConfigKey: ConfigKey in configKeyProvider.getConfigKeys()) {
  	  sharedPreferences.edit { putString(localConfigKey.key, remoteConfig[localConfigKey.key]) }  
  }
}
//...
enum class ConfigKey(val key) {
  FACEBOOK("facebook"),
  MAPBOX("mapbox"),
  THIRD_PARTY("some_service")
}
      
      



N ,  THIRD_PARTY



  . , InMemory. 20 . , ?





: InMemory / SharedPreferences / Database / WeakInMemory… .  SOLID - , ; open-closed principle , "" .





// ...
val result = remoteService.getConfig()
if (result is Result.Success) {
  val remoteConfig = result.value.clientConfig?.keys
  for(localConfigKey: ConfigKey in configKeyProvider.getConfigKeys()) {
    	configurationStorage.put(
        configKey = localConfigKey, 
        keyValue = remoteConfig[localConfigKey.key]
      )
  }
}
//....
interface ConfigKeyStorage {
   fun put(configKey: ConfigKey, keyValue: String?)
   fun get(configKey: ConfigKey): String
   fun getOrNull(configKey: ConfigKey): String?
}

internal class InMemoryConfigKeyStorage : ConfigKeyStorage {
	private val storageMap: MutableMap<ConfigKey, String?> = mutableMapOf()
	
  override fun put(configKey: ConfigKey, keyValue: String?) {
    storageMap[configKey] = keyValue
	}
  
	override fun get(configKey: ConfigKey): String = 
      requireNotNull(storageMap[configKey])
	override fun getOrNull(configKey: ConfigKey): String? = 
      storageMap[configKey]
}
      
      



, . , . - , . , N , DI/IoC , . , .





. , ( ). , - . . , .





:









, .  null



  , . , - remote.





interface UsernameRepository {
    suspend fun getUsername(): String?
}
class RemoteUsernameRepository(
    private val remoteAPI: RemoteAPI
) : UsernameRepository {
    override suspend fun getUsername(): String? = try {
        remoteAPI.getUsername()
    } catch (throwable: Throwable) {
        null
    }
}
      
      



, e  String?



   String?



. , .  getUsername() == null



  . , .  SuccessState === FailState



.





. , .





, . :





interface UsernameRepository {
    suspend fun getUsername(): String?
}
class CommonUsernameRepository(
  private val remoteRepository: UsernameRepository,
  private val localRepository: UsernameRepository
) : UsernameRepository {
    suspend fun getUsername(): String? {
        return remoteRepository.getUsername() ?: localRepository.getUsername()
    }
}
      
      



. 3 . :





  • ,  null



     - ? . .





  • ,  null



     - ?





  • ,  null



     - ?





, . . , ? - . . , , . - . , .





- .





 enum



/sealed classes



/interfaces



/abstract classes



. . - enum



/sealed classes



. - interface



/abstract classes



.





sealed class UsernameState {
	data class Success(val username: CharSequence?) : UsernameState()
  object Failed : UsernameState()
}
      
      



When

- , .





enum class NavigationFlow {
  PIN_CODE,
  MAIN_SCREEN,
  ONBOARDING,
  CHOOSE_LANGUAGE
}
fun detectNavigationFlow(): NavigationFlow {
    return when {
        authRepo.isAuthorized() -> NavigationFlow.PIN_CODE
        languageRepo.defaultLanguage != null -> NavigationFlow.CHOOSE_LANGUAGE
        onboardingStorage.isCompleted() -> NavigationFlow.MAIN_SCREEN
        else -> NavigationFlow.ONBOARDING
    }
}
      
      



. .  detectNavigationFlow



  . : , … , , .





enum class NavigationFlow {
    PIN_CODE,
    MAIN_SCREEN,
    ONBOARDING,
    CHOOSE_LANGUAGE
}

//    
sealed class State {
    data class Found(val flow: NavigationFlow) : State()
    object NotFound : State()
}

interface NavigationFlowProvider {
    //   null NavigationFlow      
    fun getNavigation(): NavigationFlow
}

//       
interface NavigationFlowResolver {
    fun resolveNavigation(): State
}

internal class SplashScreenNavigationFlowProvider(
    // Sequence -          .
    //        .
    private val resolvers: Sequence<NavigationFlowResolver>
) : NavigationFlowProvider {

    override fun getNavigation(): NavigationFlow = resolvers
        .map(NavigationFlowResolver::resolveNavigation)
        .filterIsInstance<State.Found>()
        .firstOrNull()?.flow
        //     -    
        ?: NavigationFlow.MAIN_SCREEN
}
      
      



N- when



  ChainOfResponsibililty. : . :









  1. SOLID













  2. ,





- . . . , . .  DIP  , .





 . , google. , - "". Android.





BaseActivity. , , . . , , . , - N , … , . - , .  BaseActivity



 - … > 1000 , . SOLID .





, , 2. ?





, Android SDK 14   Application.ActivityLifecycleCallbacks



  ,  Activity



. .





class App : Application(), KoinComponent {
    override fun onCreate() {
        super.onCreate()
        // ... 
        registerActivityLifecycleCallbacks(SetupKoinFragmentFactoryCallbacks())
    }
    //  Koin FragmentFactory      Koin
    private class SetupKoinFragmentFactoryCallbacks : EmptyActivityLifecycleCallbacks {
        override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
            if (activity is FragmentActivity) {
                activity.setupKoinFragmentFactory()
            }
        }
    }
}
      
      



, . :





abstract class BaseActivity(@LayoutRes contentLayoutId: Int = 0) : AppCompatActivity(contentLayoutId) {
    // attachBaseContext   protected
    override fun attachBaseContext(newBase: Context) {
        //  extension     
        super.attachBaseContext(newBase.applySelectedAppLanguage())
    }
}
      
      



BaseFragment. .  FragmentManager,  registerFragmentLifecycleCallbacks - .  FragmentLifecycleCallbacks



  -  Activty



. Koin - .





. DIP - Dagger, Koin, .. . ? - ?  5 2018 ,  FragmentFactory  .





BaseApplication. .  Flavors



  BuildType



   Application



  . ,  Application



  , , 3rd party . , .





interface Bootstrapper {
    // KoinComponent - entry point DIP        
    fun init(component: KoinComponent)
}
interface BootstrapperProvider {
    fun provide(): Set<Bootstrapper>
}
class BootstrapperLauncher(val provider: BootstrapperProvider) {
    fun launch(component: KoinComponent) {
        provider.provide().onEach { it.init(component) }
    }
}
class App : Application() {
  override fun onCreate() {
        super.onCreate()
        //     Koin
        this.get<BootstrapperLauncher>().launch(component = this)
    }
}
      
      



 Bootstrapper



  .   .





- . , . . (), , . , . , - , ?





, .





interface Validator {
    fun validate(contact: CharSequence): ValidationResult
}

sealed class ValidationResult {
    object Valid : ValidationResult()

    data class Invalid(@StringRes val errorRes: Int) : ValidationResult()
}

class PhoneNumberValidator : Validator {
    override fun validate(contact: CharSequence): ValidationResult =
        if (REGEX.matches(contact)) ValidationResult.Valid 
        else ValidationResult.Invalid(R.string.error)

    companion object {
        private val REGEX = "[0-9]{16}".toRegex()
    }
}
      
      



, ? , . . , "" , .





, N  MSISDN   E.164:





class PhoneNumberValidator : Validator {
    override fun validate(contact: CharSequence): ValidationResult =
        if (REGEX.matches(contact)) ValidationResult.Valid 
        else ValidationResult.Invalid(R.string.error)

    companion object {
        private val REGEX = "+[0-9]{16}".toRegex()
    }
}
      
      



, , . , . , . .





, :





















, , MSISDN .





interface Validator {
    fun validate(contact: CharSequence): ValidationResult
}

sealed class ValidationResult {
    object Valid : ValidationResult()

    data class Invalid(@StringRes val errorRes: Int) : ValidationResult()
}
internal class MSISDNNumberValidator : Validator {
	//...  
}
internal class E164NumberValidator : Validator {
	//...  
}
      
      



, , . :





interface ValidatorFactory {
    fun create(type: ValidatorType): Validator?

    interface ValidatorType

    companion object {
        fun create() : ValidatorFactory {
            return DefaultValidatorFactory()
        }
    }
}
object MSISDN : ValidatorFactory.ValidatorType
object E164 : ValidatorFactory.ValidatorType
private class DefaultValidatorFactory : ValidatorFactory {
    override fun create(type: ValidatorFactory.ValidatorType): Validator? = when(type) {
        is MSISDN -> MSISDNValidator()
        is E164 -> E164Validator()
        else -> null
    }
}
      
      



, . , . -  ValidatorFactory



   DefaultValidatorFactory



. .





, , SOLID. , . . ? . . . - . , - . , . 2-3 . enterprise. - . .








All Articles