https://github.com/kernel0x/kmpapp
👨💻 Kotlin Mobile Multiplatform App (Android & iOS). One Code To Rule Them All. MVVM, DI (Kodein), coroutines, livedata, ktor, serialization, mockk, detekt, ktlint, jacoco
https://github.com/kernel0x/kmpapp
android couroutine ios kodein kotlin kotlin-mpp kotlin-multiplatform ktlint ktor livedata mock mvvm-architecture swift unit-test
Last synced: 5 months ago
JSON representation
👨💻 Kotlin Mobile Multiplatform App (Android & iOS). One Code To Rule Them All. MVVM, DI (Kodein), coroutines, livedata, ktor, serialization, mockk, detekt, ktlint, jacoco
- Host: GitHub
- URL: https://github.com/kernel0x/kmpapp
- Owner: kernel0x
- Created: 2020-04-11T19:13:14.000Z (about 5 years ago)
- Default Branch: master
- Last Pushed: 2020-04-14T11:30:37.000Z (about 5 years ago)
- Last Synced: 2024-10-20T10:17:27.497Z (6 months ago)
- Topics: android, couroutine, ios, kodein, kotlin, kotlin-mpp, kotlin-multiplatform, ktlint, ktor, livedata, mock, mvvm-architecture, swift, unit-test
- Language: Kotlin
- Homepage:
- Size: 650 KB
- Stars: 39
- Watchers: 1
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# 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
```kotlin
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
```swift
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.
```kotlin
class IndexesViewModel : BaseViewModel() {private val getIndexesUseCase by Injector.instance()
private val getQuoteUseCase by Injector.instance()var getViewData = MutableLiveData(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|
|---|---|
|||