One Code To Rule Them All. Application example using Kotlin Multiplatform and MVVM pattern for both platforms.
Is used:
- layered clean architecture
- DI (Kodein)
- coroutines
- livedata
- ktor
- serialization
- mockk
- detekt, ktlint
- unit tests and jacoco
On both platforms (Android and iOS), we only need to implement an observer in which to process states. Further implementation on Android (Kotlin) and iOS (Swift):
class MainActivity : AppCompatActivity() {
  private lateinit var viewModel: IndexesViewModel
  private var adapter: IndexesAdapter = IndexesAdapter { viewModel.getQuote(it.ticker) }
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    recycler.adapter = adapter
    viewModel = ViewModelProviders.of(this).get(IndexesViewModel::class.java)
    observeViewState()
    viewModel.getMajorIndexes()
  }
  private fun observeViewState() {
    viewModel.getViewData.addObserver { updateViewState(it) }
  }
  private fun updateViewState(state: IndexesViewState) = runOnUiThread {
    when (state) {
      is Loading -> {
        Toast.makeText(this, "Loading...", Toast.LENGTH_SHORT).show()
      }
      is Error -> {
        Toast.makeText(this, state.message, Toast.LENGTH_LONG).show()
      }
      is ShowMajorIndexes -> {
        adapter.items = state.indexes
      }
      is ShowQuote -> {
        Toast.makeText(this, state.quote.dayLow + " - " + state.quote.dayHigh, Toast.LENGTH_LONG).show()
      }
    }
  }
}class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    @IBOutlet weak var tableView: UITableView!
    
    private var viewModel: IndexesViewModel!
    
    internal var indexes: [Index] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.dataSource = self
        tableView.delegate = self
        
        viewModel = IndexesViewModel()
        observeViewState()
        
        viewModel.getMajorIndexes()
    }
    
    func observeViewState() {
        viewModel.getViewData.addObserver { (state) in
            self.updateViewState(state: state as! IndexesViewState)
        }
    }
    
    func updateViewState(state: IndexesViewState) {
        switch state {
        case is Loading:
            view.displayToast("Loading...")
        case is Error:
            view.displayToast("Error")
        case is ShowMajorIndexes:
            let successState = state as! ShowMajorIndexes
            update(list: successState.indexes)
        case is ShowQuote:
            let successState = state as! ShowQuote
            view.displayToast(successState.quote.dayLow + " - " + successState.quote.dayHigh)
        default: break
        }
    }
    
    ...
    deinit {
        viewModel.onCleared()
    }
}This layer is shared by Android and iOS, and this is developed on Kotlin. Here is where we have to call the different use-cases of the domain layer. To make the call async we are using kotlin coroutines and flow.
class IndexesViewModel : BaseViewModel() {
  private val getIndexesUseCase by Injector.instance<GetIndexesUseCase>()
  private val getQuoteUseCase by Injector.instance<GetQuoteUseCase>()
  var getViewData = MutableLiveData<IndexesViewState>(Empty)
  fun getMajorIndexes() = launchInMain {
    getIndexesUseCase()
      .onStart { getViewData.postValue(Loading) }
      .flowOnBackground()
      .catch { getViewData.postValue(Error("Something went wrong")) }
      .collect { getViewData.postValue(ShowMajorIndexes(it)) }
  }
  fun getQuote(symbol: String) = launchInMain {
    getQuoteUseCase.invoke(symbol)
      .onStart { getViewData.postValue(Loading) }
      .flowOnBackground()
      .catch { getViewData.postValue(Error("Something went wrong")) }
      .collect { getViewData.postValue(ShowQuote(it)) }
  }
}In this layer, we defining the models and all the use cases that we need for our application.
For this layer we are using a repository pattern. We defining the entity models and all source of our data
For networking we are using Ktor and for JSON deserialisation Kotlinx serialization.
- Android test: ./gradlew testDebugUnitTest
- Common test on iOS (need run Simulator iPhone 8): ./gradlew iosUnitTest
To run the application use the same tools you use in Android and iOS. Just open the project with Intellj/Android Studio for the Android project and XCode for the iOS one.
| Android | iOS | 
|---|---|
|  |  |