Oct 04, 2021
12 min read
KMMT : Kotlin Multiplatform Mobile Template
Kotlin Multiplatform Mobile Development Simplified
KMMT is a KMM based project template designed to simplify the KMM development. It uses a simplified approach that can be shared both in android and iOS easily.
Primary objective of this project is to help KMM Developers & promote KMM technology
Credits : KaMP Kit
Android.mp4
iOS.mp4
IDE Requirements
IntelliJ/Android Studio – Android & Shared Module
Xcode – iOS Project
✨
Features
✨
1. Simple Networking API ( Ktor )
Create API Services using BaseAPI class. All network responses are wrapped in Either data type
<div class="highlight highlight-source-kotlin position-relative" data-snippet-clipboard-copy-content="class JsonPlaceHolderServiceAPI : BaseAPI() {
override val baseUrl: String
get() = " https: jsonplaceholder.typicode.com " suspend fun getPosts(postId: Int): Either<List, NetworkFailure> {
return doGet {
apiPath(“comments?postId=$postId”)
}
}
suspend fun setPost(post: PostModel): Either {
return doPost(post) {
apiPath(“comments”)
}
}
}
“>
class JsonPlaceHolderServiceAPI : BaseAPI () {
override val baseUrl: String
get() = " https://jsonplaceholder.typicode.com/"
suspend fun getPosts (postId : Int ): Either <List <PostModel >, NetworkFailure> {
return doGet {
apiPath(" comments?postId=$postId " )
}
}
suspend fun setPost (post : PostModel ): Either <PostModel , NetworkFailure > {
return doPost(post) {
apiPath(" comments" )
}
}
}
<div class="highlight highlight-source-kotlin position-relative" data-snippet-clipboard-copy-content="class BreedServiceAPI : BaseAPI() {
override val baseUrl: String
get() = " https: dog.ceo " suspend fun getBreeds(): Either<List, NetworkFailure> {
return doGet {
apiPath(“api/breeds/list/all”)
}.flatMap { breedResult ->
//Converting BreedResult to List
Either.Success(
breedResult.message.keys
.sorted().toList()
.map { TBreed(0L, name = it.toWordCaps(), false) }
)
}
}
}
“>
class BreedServiceAPI : BaseAPI () {
override val baseUrl: String
get() = " https://dog.ceo/"
suspend fun getBreeds (): Either <List <TBreed >, NetworkFailure> {
return doGet<BreedResult > {
apiPath(" api/breeds/list/all" )
}.flatMap { breedResult ->
// Converting BreedResult to List
Either .Success (
breedResult.message.keys
.sorted().toList()
.map { TBreed (0L , name = it.toWordCaps(), false ) }
)
}
}
}
Run code (Networking calls, Heavy calculations, Large dataSets from local DB, etc..) in Background thread and get the result in UI thread.
runOnBackground {
// Code to execute in background
}
Return value from background
}
or
runOnBackgroundWithResult {
//Code to execute in background with return
}.resultOnBackground { result ->
}
“>
runOnBackgroundWithResult {
// Code to execute in background with return
}.resultOnUI { result ->
}
or
runOnBackgroundWithResult {
// Code to execute in background with return
}.resultOnBackground { result ->
}
<div class="highlight highlight-source-kotlin position-relative" data-snippet-clipboard-copy-content="class PostViewModel(view: LoginView) : BaseViewModel(view) {
fun getPostsFromAPI() {
runOnBackgroundWithResult {
JsonPlaceHolderServiceAPI().getPosts(1) //getPost returns data so return statement is not needed
//or
// return@runOnBackgroundWithResult JsonPlaceHolderServiceAPI().getPosts(1)
}.resultOnUI {
getView()?.showPopUpMessage(
” First Post Details”, “Username : ${it.first().name}\n email : ${it.first().email}” ) } } fun savePost() { val post = PostModel(“Post Body”, “[email protected] ”, 100, “Jitty”, 6) runOnBackgroundWithResult { JsonPlaceHolderServiceAPI().setPost(post) }.resultOnUI { getView()?.showPopUpMessage(“Saved Post Details”, “Name : ${it.name}\n email : ${it.email}”) } } } “>
class PostViewModel (view : LoginView ) : BaseViewModel(view) {
fun getPostsFromAPI () {
runOnBackgroundWithResult {
JsonPlaceHolderServiceAPI ().getPosts(1 ) // getPost returns data so return statement is not needed
// or
// [email protected] JsonPlaceHolderServiceAPI().getPosts(1)
}.resultOnUI {
getView()?.showPopUpMessage(
" First Post Details" ,
" Username : ${it.first().name} \n email : ${it.first().email} "
)
}
}
fun savePost () {
val post = PostModel (" Post Body" , " [email protected] " , 100 , " Jitty" , 6 )
runOnBackgroundWithResult {
JsonPlaceHolderServiceAPI ().setPost(post)
}.resultOnUI {
getView()?.showPopUpMessage(" Saved Post Details" , " Name : ${it.name} \n email : ${it.email} " )
}
}
}
3. Multiplatform Bundle : Object Passing B/W Activities or ViewControllers
View Model can pass objects & values from Activity to Activity (Android) or ViewController to ViewController (iOS)
Send Values From 1st View Model
// 1st View Model
var userModel = UserModel (" [email protected] " , " Jitty" , " Andiyan" )
var bundle = Bundle {
putStringExtra(HomeViewModel .USER_NAME , username.toString())
putSerializableExtra(HomeViewModel .USER_OBJECT , userModel, UserModel .serializer())
}
getView()?.navigateToHomePage(bundle)
// 1st View
fun navigateToHomePage (bundle : BundleX )
// 1st Activity : Android
override fun navigateToHomePage (bundle : BundleX ) {
openActivity(HomeActivity ::class .java, bundle)
finish()
}
// 1st ViewContoller : iOS
func navigateToHomePage (bundle: BundleX ) {
openViewController(newViewControllerName: " HomeViewController" , bundle: bundle)
}
Retrieve Values From 2nd View Model
<div class="highlight highlight-source-kotlin position-relative" data-snippet-clipboard-copy-content=" // 2nd View Model
class HomeViewModel(view: HomeView) : BaseViewModel(view) {
companion object BundleKeys {
const val USER_NAME = ” USERNAME” const val USER_OBJECT=”USEROBJ” } override fun onStartViewModel() { getBundleValue(USER_NAME)?.let { username ->
}
getBundleValue(USER_OBJECT)?.let { userModel ->
}
}
}
“>
// 2nd View Model
class HomeViewModel (view : HomeView ) : BaseViewModel(view) {
companion object BundleKeys {
const val USER_NAME = " USERNAME"
const val USER_OBJECT = " USEROBJ"
}
override fun onStartViewModel () {
getBundleValue<String >(USER_NAME )?.let { username ->
}
getBundleValue<UserModel >(USER_OBJECT )?.let { userModel ->
}
}
}
4. Platform Blocks
Execute anything specific to a particular platform using Platform Blocks
runOnAndroid {
}
runOniOS {
}
Use toJsonString and toObject functions for instant serialization.
Objects to String Serialization
var userModel = UserModel("[email protected] ", "Jitty", "Andiyan")
var jsonString = userModel.toJsonString(UserModel.serializer())
String to Object Serialization
<div class="snippet-clipboard-content position-relative" data-snippet-clipboard-copy-content=" var userModel = jsonString.toObject()
or
var userModel:UserModel = jsonString.toObject()
or
var userModel = jsonString.toObject(UserModel.serializer())
“>
var userModel = jsonString.toObject()
or
var userModel:UserModel = jsonString.toObject()
or
var userModel = jsonString.toObject(UserModel.serializer())
Use storeValue and getStoreValue functions for storing and retrieving Key-Value respectively
Storing Key-Value pair
var userModel = UserModel("[email protected] ", "Jitty", "Andiyan")
storeValue {
putString("Key1","Value")
putBoolean("Key2",false)
putSerializable("Key3",userModel,UserModel.serializer())
}
Retrieve Value using Key
<div class="snippet-clipboard-content position-relative" data-snippet-clipboard-copy-content=" var stringValue = getStoreValue(” Key1″) or var stringValue:String? = getStoreValue(“Key1”) var boolValue = getStoreValue(“Key2”)
var userModel = getStoreValue(“Key3”,UserModel.serializer())
“>
var stringValue = getStoreValue("Key1")
or
var stringValue:String? = getStoreValue("Key1")
var boolValue = getStoreValue("Key2")
var userModel = getStoreValue("Key3",UserModel.serializer())
7. LiveData & LiveDataObservable ( LiveData )
LiveData follows the observer pattern. LiveData notifies Observer objects when underlying data changes. You can consolidate your code to update the UI in these Observer objects. That way, you don’t need to update the UI every time the app data changes because the observer does it for you.
<div class="snippet-clipboard-content position-relative" data-snippet-clipboard-copy-content=" //Sources
var premiumManager = PremiumManager()
var premiumManagerBoolean = PremiumManagerBoolean()
//Create Observer & Observe
var subscriptionLiveDataObservable = observe {
getView()?.setSubscriptionLabel(it)
}
//Adding Sources
subscriptionLiveDataObservable.addSource(premiumManager.premium())
or
//Adding Sources with converter (Boolean to String)
subscriptionLiveDataObservable.addSource(premiumManagerBoolean.isPremium()){
if (it)
{
return@addSource ” Premium” }else{ return@addSource “Free” } } Update source states premiumManager.becomePremium() premiumManagerBoolean.becomeFree() premiumManager.becomeFree() premiumManagerBoolean.becomePremium() “>
//Sources
var premiumManager = PremiumManager()
var premiumManagerBoolean = PremiumManagerBoolean()
//Create Observer & Observe
var subscriptionLiveDataObservable = observe {
getView()?.setSubscriptionLabel(it)
}
//Adding Sources
subscriptionLiveDataObservable.addSource(premiumManager.premium())
or
//Adding Sources with converter (Boolean to String)
subscriptionLiveDataObservable.addSource(premiumManagerBoolean.isPremium()){
if (it)
{
[email protected] "Premium"
}else{
[email protected] "Free"
}
}
//Update source states
premiumManager.becomePremium()
premiumManagerBoolean.becomeFree()
premiumManager.becomeFree()
premiumManagerBoolean.becomePremium()
<div class="snippet-clipboard-content position-relative" data-snippet-clipboard-copy-content="class PremiumManager {
private val premium = MutableLiveDataX()
fun premium(): LiveDataX {
return premium
}
fun becomePremium() {
premium.value = ” premium” } fun becomeFree() { premium.value=”free” } } class PremiumManagerBoolean { private val premium = MutableLiveDataX()
fun isPremium(): LiveDataX {
return premium
}
fun becomePremium() {
premium.value = true
}
fun becomeFree() {
premium.value = false
}
}
“>
class PremiumManager {
private val premium = MutableLiveDataX()
fun premium(): LiveDataX {
return premium
}
fun becomePremium() {
premium.value = "premium"
}
fun becomeFree() {
premium.value = "free"
}
}
class PremiumManagerBoolean {
private val premium = MutableLiveDataX()
fun isPremium(): LiveDataX {
return premium
}
fun becomePremium() {
premium.value = true
}
fun becomeFree() {
premium.value = false
}
}
8. Observe with DBHelper ( Local Database : SQLite – SQLDelight )
Use ‘asFlow()’ extension from DBHelper class to observe a query data
<div class="highlight highlight-source-kotlin position-relative" data-snippet-clipboard-copy-content="class BreedTableHelper : DBHelper() {
fun getAllBreeds(): Flow<List> =
localDB.tBreedQueries
.selectAll()
.asFlow()
.mapToList()
.flowOn(Dispatchers_Default)
suspend fun insertBreeds(breeds: List) {
…
}
fun selectById(id: Long): Flow<List> =
localDB.tBreedQueries
.selectById(id)
.asFlow()
.mapToList()
.flowOn(Dispatchers_Default)
suspend fun deleteAll() {
…
}
suspend fun updateFavorite(breedId: Long, favorite: Boolean) {
localDB.transactionWithContext(Dispatchers_Default) {
localDB.tBreedQueries.updateFavorite(favorite, breedId)
}
}
}
“>
class BreedTableHelper : DBHelper () {
fun getAllBreeds (): Flow <List <TBreed >> =
localDB.tBreedQueries
.selectAll()
.asFlow()
.mapToList()
.flowOn(Dispatchers_Default )
suspend fun insertBreeds (breeds : List <TBreed >) {
.. .
}
fun selectById (id : Long ): Flow <List <TBreed >> =
localDB.tBreedQueries
.selectById(id)
.asFlow()
.mapToList()
.flowOn(Dispatchers_Default )
suspend fun deleteAll () {
.. .
}
suspend fun updateFavorite (breedId : Long , favorite : Boolean ) {
localDB.transactionWithContext(Dispatchers_Default ) {
localDB.tBreedQueries.updateFavorite(favorite, breedId)
}
}
}
<div class="highlight highlight-source-kotlin position-relative" data-snippet-clipboard-copy-content="class BreedViewModel(view: BreedView) : BaseViewModel(view) {
private lateinit var breedTableHelper: BreedTableHelper
private lateinit var breedLiveDataObservable: LiveDataObservable<Either<List, Failure>>
private lateinit var breedListCache: BreedListCache
override fun onStartViewModel() {
breedTableHelper = BreedTableHelper()
breedListCache = BreedListCache(getBackgroundCoroutineScope())
breedLiveDataObservable = observe { breedList ->
breedList.either({
getView()?.showPopUpMessage(it.message)
getView()?.stopRefreshing()
}, {
getView()?.refreshBreedList(it)
getView()?.stopRefreshing()
})
}
refreshBreedListCache(forceRefresh = false)
observeBreedsTable()
}
private fun observeBreedsTable() {
//get Data from db with observe (Flow)
runOnBackground {
//Each refreshBreedListCache will trigger collect
breedTableHelper.getAllBreeds().collect {
breedLiveDataObservable.setValue(Either.Success(it))
}
}
}
private fun refreshBreedListCache(forceRefresh: Boolean) {
breedListCache.cacheData(Unit, forceRefresh)
{ cachedResult ->
cachedResult.either({
breedLiveDataObservable.setValue(Either.Failure(it))
}, {
println(” Cache Table updated : $it”) }) } } } “>
class BreedViewModel (view : BreedView ) : BaseViewModel(view) {
private lateinit var breedTableHelper: BreedTableHelper
private lateinit var breedLiveDataObservable: LiveDataObservable <Either <List <TBreed >, Failure >>
private lateinit var breedListCache: BreedListCache
override fun onStartViewModel () {
breedTableHelper = BreedTableHelper ()
breedListCache = BreedListCache (getBackgroundCoroutineScope())
breedLiveDataObservable = observe { breedList ->
breedList.either({
getView()?.showPopUpMessage(it.message)
getView()?.stopRefreshing()
}, {
getView()?.refreshBreedList(it)
getView()?.stopRefreshing()
})
}
refreshBreedListCache(forceRefresh = false )
observeBreedsTable()
}
private fun observeBreedsTable () {
// get Data from db with observe (Flow)
runOnBackground {
// Each refreshBreedListCache will trigger collect
breedTableHelper.getAllBreeds().collect {
breedLiveDataObservable.setValue(Either .Success (it))
}
}
}
private fun refreshBreedListCache (forceRefresh : Boolean ) {
breedListCache.cacheData(Unit , forceRefresh)
{ cachedResult ->
cachedResult.either({
breedLiveDataObservable.setValue(Either .Failure (it))
}, {
println (" Cache Table updated : $it " )
})
}
}
}
9. Useful Functional Programming
use Either data type to represent a value of one of two possible types (a disjoint union). Instances of Either are either an instance of Failure or Success
<div class="highlight highlight-source-kotlin position-relative" data-snippet-clipboard-copy-content=" Either
“>
Either <SuccessType , FailureType >
convert or map SuccessType using flatMap or map
<div class="highlight highlight-source-kotlin position-relative" data-snippet-clipboard-copy-content="var result = doGet<List> {
apiPath(” jittya jsonserver users?username=${credentails.username}&password=${credentails.password}”) } return result.flatMap { convert List to Boolean Either.Success(it.any { it.username == credentails.username && it.password == credentails.password }) } “>
var result = doGet<List <UserModel >> {
apiPath(" jittya/jsonserver/users?username=${credentails.username} &password=${credentails.password} " )
}
return result.flatMap {
// convert List to Boolean
Either .Success (it.any { it.username == credentails.username && it.password == credentails.password })
}
use either blocks( either or eitherAsync [for suspended method support] ) to define failure & success functionalities
//Success
if (isAuthenticated) {
var userModel = UserModel(” [email protected] ”, “Jitty”, “Andiyan”) var bundle = Bundle { putStringExtra(HomeViewModel.USER_NAME, username.toString()) putSerializableExtra(HomeViewModel.USER_OBJECT, userModel, UserModel.serializer()) } getView()?.navigateToHomePage(bundle) } else { getView()?.showPopUpMessage(“Login Failed”) } }) “>
authenticatedResult.either({
// Failure
getView()?.showPopUpMessage(it.message)
}, { isAuthenticated ->
// Success
if (isAuthenticated) {
var userModel = UserModel (" [email protected] " , " Jitty" , " Andiyan" )
var bundle = Bundle {
putStringExtra(HomeViewModel .USER_NAME , username.toString())
putSerializableExtra(HomeViewModel .USER_OBJECT , userModel, UserModel .serializer())
}
getView()?.navigateToHomePage(bundle)
} else {
getView()?.showPopUpMessage(" Login Failed" )
}
})
10. Data Cache Helper
Use BaseDataCache to implement data caching (remote to local). call cacheData function to get and save data
<div class="highlight highlight-source-kotlin position-relative" data-snippet-clipboard-copy-content="class BreedListCache(backgroundCoroutineScope: CoroutineScope) :
BaseDataCache<Unit, List>(backgroundCoroutineScope, ” BREED_SYNC_TIME”) { override suspend fun getData(param: Unit): Either<List, Failure> {
//get data from remote (using api)
return BreedServiceAPI().getBreeds()
}
override suspend fun saveData(data: List): Either {
//save remote data in Local database
return try {
BreedTableHelper().insertBreeds(data)
Either.Success(true)
} catch (e: Exception) {
Either.Failure(DataBaseFailure(e))
}
}
}
“>
class BreedListCache (backgroundCoroutineScope : CoroutineScope ) :
BaseDataCache <Unit , List <TBreed >>(backgroundCoroutineScope, " BREED_SYNC_TIME" ) {
override suspend fun getData (param : Unit ): Either <List <TBreed >, Failure> {
// get data from remote (using api)
return BreedServiceAPI ().getBreeds()
}
override suspend fun saveData (data : List <TBreed >): Either <Boolean , Failure > {
// save remote data in Local database
return try {
BreedTableHelper ().insertBreeds(data )
Either .Success (true )
} catch (e: Exception ) {
Either .Failure (DataBaseFailure (e))
}
}
}
cachedResult.either({ failure ->
println(” Cache failed : $failure”) }, { success ->
println(“Cache updated : $success”)
})
}
}
“>
var breedListCache = BreedListCache (getBackgroundCoroutineScope())
private fun refreshBreedListCache (forceRefresh : Boolean ) {
// breedListCache.cacheData(Unit, forceRefresh)
// or
breedListCache.cacheData(Unit , forceRefresh)
{ cachedResult ->
cachedResult.either({ failure ->
println (" Cache failed : $failure " )
}, { success ->
println (" Cache updated : $success " )
})
}
}
How to use
Shared Module (Business Logics & UI Binding Methods) :
Step 1 : Define View
Create a View interface by extending from BaseView.
Define UI binding functions in View interface.
<div class="highlight highlight-source-kotlin position-relative" data-snippet-clipboard-copy-content="interface LoginView : BaseView {
fun setLoginPageLabel(msg: String)
fun setUsernameLabel(usernameLabel: String)
fun setPasswordLabel(passwordLabel: String)
fun setLoginButtonLabel(loginLabel: String)
fun getEnteredUsername(): String
fun getEnteredPassword(): String
fun setLoginButtonClickAction(onLoginClick: KFunction0)
fun navigateToHomePage(bundle: BundleX)
}
“>
interface LoginView : BaseView {
fun setLoginPageLabel (msg : String )
fun setUsernameLabel (usernameLabel : String )
fun setPasswordLabel (passwordLabel : String )
fun setLoginButtonLabel (loginLabel : String )
fun getEnteredUsername (): String
fun getEnteredPassword (): String
fun setLoginButtonClickAction (onLoginClick : KFunction0 <Unit >)
fun navigateToHomePage (bundle : BundleX )
}
Step 2 : Define ViewModel
Create a ViewModel class by extending from BaseViewModel with View as Type.
Define your business logic in ViewModel class.
<div class="highlight highlight-source-kotlin position-relative" data-snippet-clipboard-copy-content="class LoginViewModel(view: LoginView) : BaseViewModel(view) {
override fun onStartViewModel() {
getView()?.setLoginPageLabel(” Login : ${Platform().platform}”) getView()?.setUsernameLabel(“Enter Username”) getView()?.setPasswordLabel(“Enter Password”) getView()?.setLoginButtonLabel(“Login”) getView()?.setLoginButtonClickAction(this::onLoginButtonClick) } fun onLoginButtonClick() { getView()?.showLoading(“authenticating…”) val username = getView()?.getEnteredUsername() val password = getView()?.getEnteredPassword() checkValidation(username, password) } fun checkValidation(username: String?, password: String?) { if (username.isNullOrBlank().not() && password.isNullOrBlank().not()) { val credentials = CredentialsModel(username.toString(), password.toString()) runOnBackgroundWithResult { JsonPlaceHolderServiceAPI().authenticate(credentials) }.resultOnUI { authenticatedResult ->
getView()?.dismissLoading()
authenticatedResult.either({
getView()?.showPopUpMessage(it.message)
}, { isAuthenticated ->
if (isAuthenticated) {
var bundle = Bundle {
putStringExtra(HomeViewModel.USER_NAME, username.toString())
}
getView()?.navigateToHomePage(bundle)
} else {
getView()?.showPopUpMessage(
“Login Failed”
)
}
})
}
} else {
getView()?.showPopUpMessage(“Validation Failed”, “Username or Password is empty”)
}
}
}
“>
class LoginViewModel (view : LoginView ) : BaseViewModel(view) {
override fun onStartViewModel () {
getView()?.setLoginPageLabel(" Login : ${Platform().platform} " )
getView()?.setUsernameLabel(" Enter Username" )
getView()?.setPasswordLabel(" Enter Password" )
getView()?.setLoginButtonLabel(" Login" )
getView()?.setLoginButtonClickAction(this ::onLoginButtonClick)
}
fun onLoginButtonClick () {
getView()?.showLoading(" authenticating..." )
val username = getView()?.getEnteredUsername()
val password = getView()?.getEnteredPassword()
checkValidation(username, password)
}
fun checkValidation (username : String? , password : String? ) {
if (username.isNullOrBlank().not () && password.isNullOrBlank().not ()) {
val credentials = CredentialsModel (username.toString(), password.toString())
runOnBackgroundWithResult {
JsonPlaceHolderServiceAPI ().authenticate(credentials)
}.resultOnUI { authenticatedResult ->
getView()?.dismissLoading()
authenticatedResult.either({
getView()?.showPopUpMessage(it.message)
}, { isAuthenticated ->
if (isAuthenticated) {
var bundle = Bundle {
putStringExtra(HomeViewModel .USER_NAME , username.toString())
}
getView()?.navigateToHomePage(bundle)
} else {
getView()?.showPopUpMessage(
" Login Failed"
)
}
})
}
} else {
getView()?.showPopUpMessage(" Validation Failed" , " Username or Password is empty" )
}
}
}
Android Module UI Binding :
Step 3 : Define Android View
Create new activity by extending from KMMActivity with ViewModel as Type.
Implement created View interface in activity.
Implement all necessary methods from View & KMMActivity.
Implement LoginView & Bind UI Controls
<div class="highlight highlight-source-kotlin position-relative" data-snippet-clipboard-copy-content="class LoginActivity : KMMActivity(), LoginView {
//Generated Methods from KMMActivity based on LoginViewModel
override fun initializeViewModel(): LoginViewModel {
return LoginViewModel(this)
}
override fun viewBindingInflate(): ActivityMainBinding {
return ActivityMainBinding.inflate(layoutInflater)
}
//Generated Methods from LoginView
override fun setLoginPageLabel(msg: String) {
binding.textView.text = msg
}
override fun setUsernameLabel(usernameLabel: String) {
binding.usernameET.hint = usernameLabel
}
override fun setPasswordLabel(passwordLabel: String) {
binding.passwordET.hint = passwordLabel
}
override fun getEnteredUsername(): String {
return binding.usernameET.text.toString()
}
override fun getEnteredPassword(): String {
return binding.passwordET.text.toString()
}
override fun setLoginButtonClickAction(onLoginClick: KFunction0) {
binding.loginBtn.setClickAction(onLoginClick)
}
override fun setLoginButtonLabel(loginLabel: String) {
binding.loginBtn.text = loginLabel
}
override fun navigateToHomePage(bundle: BundleX) {
openActivity(HomeActivity::class.java, bundle)
finish()
}
}
“>
class LoginActivity : KMMActivity <LoginViewModel , ActivityMainBinding >(), LoginView {
// Generated Methods from KMMActivity based on LoginViewModel
override fun initializeViewModel (): LoginViewModel {
return LoginViewModel (this )
}
override fun viewBindingInflate (): ActivityMainBinding {
return ActivityMainBinding .inflate(layoutInflater)
}
// Generated Methods from LoginView
override fun setLoginPageLabel (msg : String ) {
binding.textView.text = msg
}
override fun setUsernameLabel (usernameLabel : String ) {
binding.usernameET.hint = usernameLabel
}
override fun setPasswordLabel (passwordLabel : String ) {
binding.passwordET.hint = passwordLabel
}
override fun getEnteredUsername (): String {
return binding.usernameET.text.toString()
}
override fun getEnteredPassword (): String {
return binding.passwordET.text.toString()
}
override fun setLoginButtonClickAction (onLoginClick : KFunction0 <Unit >) {
binding.loginBtn.setClickAction(onLoginClick)
}
override fun setLoginButtonLabel (loginLabel : String ) {
binding.loginBtn.text = loginLabel
}
override fun navigateToHomePage (bundle : BundleX ) {
openActivity(HomeActivity ::class .java, bundle)
finish()
}
}
iOS Module UI Binding (Xcode) :
Step 4 : Define iOS View
Create new viewcontroller by extending from KMMUIViewController.
Implement created View interface in viewcontroller.
Implement all necessary methods from View & KMMUIViewController.
Implement LoginView & Bind UI Controls
String
{
usernameTF.errorMessage = ” ” return usernameTF.text ?? “” } func getEnteredPassword() -> String
{
return passwordTF.text ?? “”
}
func setLoginButtonClickAction(onLoginClick: @escaping() -> KotlinUnit)
{
loginBtn.setClickAction(action: onLoginClick)
}
func setLoginButtonLabel(loginLabel: String)
{
loginBtn.setTitle(loginLabel, for: UIControl.State.normal)
}
//Generated Methods from KMMUIViewController
override func initializeViewModel() -> BaseViewModel
{
return LoginViewModel(view: self).getViewModel()
}
func navigateToHomePage(bundle: BundleX)
{
openViewController(newViewControllerName: “HomeViewController”, bundle: bundle)
}
}
“>
class LoginViewController : KMMUIViewController , LoginView {
@IBOutlet weak
var usernameTF: UITextFieldX !
@IBOutlet weak
var passwordTF: UITextFieldX !
@IBOutlet weak
var textlabel: UILabel !
@IBOutlet weak
var loginBtn: UIButton !
override func viewDidLoad()
{
super .viewDidLoad()
// Do any additional setup after loading the view.
}
// Generated Methods from LoginView
func setLoginPageLabel(msg: String )
{
textlabel.text = msg
}
func setUsernameLabel(usernameLabel: String )
{
usernameTF.placeholder = usernameLabel
}
func setPasswordLabel(passwordLabel: String )
{
passwordTF.placeholder = passwordLabel
}
func getEnteredUsername() -> String
{
usernameTF.errorMessage = " "
return usernameTF.text ?? " "
}
func getEnteredPassword() -> String
{
return passwordTF.text ?? " "
}
func setLoginButtonClickAction(onLoginClick: @escaping() -> KotlinUnit )
{
loginBtn.setClickAction(action: onLoginClick)
}
func setLoginButtonLabel(loginLabel: String )
{
loginBtn.setTitle(loginLabel, for : UIControl .State .normal)
}
// Generated Methods from KMMUIViewController
override func initializeViewModel() -> BaseViewModel <BaseView >
{
return LoginViewModel (view: self).getViewModel()
}
func navigateToHomePage(bundle: BundleX )
{
openViewController(newViewControllerName: " HomeViewController" , bundle: bundle)
}
}
Subscribe for upcoming details and features…
GitHub
https://github.com/jittya/KMMT
Previous Post
Example Jetpack Compose Android App, that uses the newest mechanisms, like StateFlow, SharedFlow, etc
Next Post
Simulate the running route of each player on the playground, and can be timed with a stopwatch
Kotlin Multiplatform Mobile App Template
13 November 2021
A template for Kotlin Multiplatform Projects
08 June 2022
Template for using Processing 4 with the Gradle Build Tool and Kotlin
28 May 2022