Skip to content

A simple crypto app which uses CoinCap Api, and it also supports relative Ui

Notifications You must be signed in to change notification settings

kenkoro/crypto-tracker

Repository files navigation

Best practices from this project

A good Ui is a dumb Ui

It's when you have a domain-defined ui models together with a Ui compatible models, so your Ui doesn't need to think of formatting, or calculating the right data look to display. Example for the coin model:

Domain-defined model

data class Coin(
  val id: String,
  val rank: Int,
  val name: String,
  val symbol: String,
  val marketCapUsd: Double,
  val priceUsd: Double,
  val changePercent24Hr: Double,
)

Ui compatible model

data class CoinUi(
  val id: String,
  val rank: Int,
  val name: String,
  val symbol: String,
  val marketCapUsd: DisplayableNumber,
  val priceUsd: DisplayableNumber,
  val changePercent24Hr: DisplayableNumber,
  @DrawableRes val iconRes: Int,
)


data class DisplayableNumber(
  val value: Double,
  val formatted: String,
)


fun Coin.toCoinUi(): CoinUi {
  return CoinUi(
    id = id,
    rank = rank,
    name = name,
    symbol = symbol,
    marketCapUsd = marketCapUsd.toDisplayableNumber(),
    priceUsd = priceUsd.toDisplayableNumber(),
    changePercent24Hr = changePercent24Hr.toDisplayableNumber(),
    iconRes = getDrawableIdForCoin(symbol),
  )
}


fun Double.toDisplayableNumber(): DisplayableNumber {
  val formatter =
    NumberFormat.getNumberInstance(Locale.getDefault()).apply {
      minimumFractionDigits = 2
      maximumFractionDigits = 2
    }


  return DisplayableNumber(
    value = this,
    formatted = formatter.format(this),
  )
}

New Result class

Result utility class with a concrete set of errors, and error messages.

P.s.: Error is a plain marker interface just for you

typealias RootError = Error

sealed interface Result<out D, out E : Error> {
  data class Success<out D>(val data: D) : Result<D, Nothing>

  data class Error<out E : RootError>(val error: E) : Result<Nothing, E>
}

Here, we use out to mark D, and E as covarient type parameters - meaning, if we have SubD : D and SubE : E, then Result<SubD, SubE> is a subtype of Result<D, E>, where the hierarchy of E looks like Error -> E -> SubE. Notice that Nothing is a subtype of all types in Kotlin. This allows us to write something like:

suspend inline fun <reified T> responseToResult(response: HttpResponse): Result<T, NetworkError> {
  return when (response.status.value) {
    in 200..299 -> {
      try {
        Result.Success(response.body<T>())
      } catch (e: NoTransformationFoundException) {
        Result.Error(NetworkError.Serialization)
      }
    }
    // TODO
  }
}

P.s.: Want to map your enum error value to a specific string resource? Check this out

Dto as a way of independence for your domain

You should use dto classes instead of your domain models, so that you don't rely on a specific Api implementation. I mean, your app shouldn't be heavily dependent on other codebases, because if the 3rd party code changes, it can affect your app well-being.

About

A simple crypto app which uses CoinCap Api, and it also supports relative Ui

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages