+
Skip to content

👨‍💻 Kotlin Mobile Multiplatform App (Android & iOS). One Code To Rule Them All. MVVM, DI (Kodein), coroutines, livedata, ktor, serialization, mockk, detekt, ktlint, jacoco

Notifications You must be signed in to change notification settings

kernel0x/kmpapp

Repository files navigation

Kotlin Mobile Multiplatform App (Android & iOS)

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

Presentation layer (Android & iOS)

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):

Android

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()
      }
    }
  }
}

iOS

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()
    }
}

Presentation Layer - ViewModels (Shared Code)

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)) }
  }
}

Domain Layer — Models & UseCases (Shared Code)

In this layer, we defining the models and all the use cases that we need for our application.

Data Layer — Repository Pattern (Shared Code)

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.

Running unit tests

  • Android test: ./gradlew testDebugUnitTest
  • Common test on iOS (need run Simulator iPhone 8): ./gradlew iosUnitTest

Running the app

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.

Screenshots

Android iOS
android-app ios-app

About

👨‍💻 Kotlin Mobile Multiplatform App (Android & iOS). One Code To Rule Them All. MVVM, DI (Kodein), coroutines, livedata, ktor, serialization, mockk, detekt, ktlint, jacoco

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载