{"id":13537387,"url":"https://github.com/jittya/KMMT","last_synced_at":"2025-04-02T04:30:48.569Z","repository":{"id":41546088,"uuid":"361707096","full_name":"jittya/KMMT","owner":"jittya","description":"Kotlin Multiplatform Mobile App Template","archived":false,"fork":false,"pushed_at":"2022-07-24T17:04:33.000Z","size":16108,"stargazers_count":240,"open_issues_count":0,"forks_count":13,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-11-03T02:32:55.968Z","etag":null,"topics":["android","cross-platform","ios","kotlin","kotlin-multiplatform","kotlin-native","swift"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jittya.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-04-26T10:22:05.000Z","updated_at":"2024-10-24T17:21:13.000Z","dependencies_parsed_at":"2022-07-26T10:48:33.087Z","dependency_job_id":null,"html_url":"https://github.com/jittya/KMMT","commit_stats":null,"previous_names":[],"tags_count":0,"template":true,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jittya%2FKMMT","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jittya%2FKMMT/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jittya%2FKMMT/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jittya%2FKMMT/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jittya","download_url":"https://codeload.github.com/jittya/KMMT/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246756920,"owners_count":20828795,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","cross-platform","ios","kotlin","kotlin-multiplatform","kotlin-native","swift"],"created_at":"2024-08-01T09:00:58.433Z","updated_at":"2025-04-02T04:30:43.560Z","avatar_url":"https://github.com/jittya.png","language":"Kotlin","funding_links":[],"categories":["Libraries"],"sub_categories":["Architecture"],"readme":"# KMMT : Kotlin Multiplatform Mobile Template\n\n## _Kotlin Multiplatform Mobile Development Simplified_\n\n[![Kotlin](https://img.shields.io/badge/kotlin-1.7.0-green.svg?logo=kotlin)](http://kotlinlang.org)\n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)\n[![Platform](https://img.shields.io/badge/Platform-Android_\u0026_iOS-green.svg)](https://kotlinlang.org/lp/mobile/)\n\nKMMT is a Multi-Module KMM based project template designed to simplify the KMM development. It uses a simplified approach that can be\nshared both in android and iOS easily. This template include network module, persistence module, resource module, analytics module ( with ios native library integration), domain module, presenter module etc.\n\n_Primary objective of this project is to help KMM Developers \u0026 promote KMM technology_\n\n[![image](https://kotlinlang.org/lp/mobile/static/sdk-392342a1bb7fde8faa08e60b07d5c802.svg)](https://kotlinlang.org/lp/mobile/)\n\n#### KMMT Module Structure\n\n![img.png](img.png)\n\n![img_1.png](img_1.png)\n\nCredits : [KaMP Kit]\n\nhttps://user-images.githubusercontent.com/9760688/116871405-d2815b80-ac31-11eb-89cd-cbc92dd42d8c.mp4\n\nhttps://user-images.githubusercontent.com/9760688/116871609-24c27c80-ac32-11eb-93bb-387b4f5918c2.mp4\n\n##### IDE Requirements\n\nIntelliJ/Android Studio - Android \u0026 Shared Module\n\nXcode - iOS Project\n\n## ✨Features ✨\n\n#### 1. Simple Networking API  ( [Ktor] )\n#### 2. Async Task Helper ( [Kotlinx.Coroutines] )\n#### 3. Multiplatform Bundle : Object Passing B/W Activities or ViewControllers\n#### 4. Platform Blocks\n#### 5. Object Serialization Helper ( [Kotlinx.Serialization] )\n#### 6. Key Value Store ( [Multiplatform Settings] )\n#### 7. LiveData \u0026 LiveDataObservable ( [LiveData] )\n#### 8. Observe with DBHelper ( Local Database : SQLite - [SQLDelight] )\n#### 9. Useful Functional Programming\n#### 10. Data Cache Helper\n#### 11. Persistence Module - Database ( [Realm] \u0026 [SQLDelight] ) and Key Value Store ( [Multiplatform Settings] )\n#### 12. Injector Module - Dependency Injection ( [Koin] )\n#### 13. Resource Module - strings and colors ( [MokoResources] )\n#### 14. Analytics Module - _Mixpanel_ and _UXCam_ ( iOS \u0026 Android Native Library Integrated )\n\n### 1. Simple Networking API  ( [Ktor] )\n\nCreate API Services using BaseAPI class. All network responses are wrapped in *Either* data type\n\n```kotlin\nclass JsonPlaceHolderServiceAPI : BaseAPI() {\n\n    override val baseUrl: String\n        get() = \"https://jsonplaceholder.typicode.com/\"\n\n    suspend fun getPosts(postId: Int): Either\u003cList\u003cPostModel\u003e, NetworkFailure\u003e {\n        return doGet(\"comments?postId=$postId\")\n    }\n\n    suspend fun setPost(post: PostModel): Either\u003cPostModel, NetworkFailure\u003e {\n        return doPost(\"comments\", post)\n    }\n}\n```\n\n```kotlin\nclass BreedServiceAPI : BaseAPI() {\n    override val baseUrl: String\n        get() = \"https://dog.ceo/\"\n\n    suspend fun getBreeds(): Either\u003cList\u003cBreed\u003e, NetworkFailure\u003e {\n        return doGet\u003cBreedResult\u003e(\"api/breeds/list/all\").flatMap { breedResult -\u003e\n            //Converting BreedResult to List\u003cTBreed\u003e\n            Either.Success(\n                breedResult.message.keys\n                    .sorted().toList()\n                    .map { Breed(0L, name = it.toWordCaps(), false) }\n            )\n        }\n    }\n}\n```\n\n### 2. Async Task Helper ( [Kotlinx.Coroutines] )\n\nRun code (Networking calls, Heavy calculations, Large dataSets from local DB, etc..) in Background thread and get the\nresult in UI thread.\n\n```kotlin\nrunOnBackground {\n    //Code to execute in background\n}\n```\n\nReturn value from background\n\n```kotlin\nrunOnBackgroundWithResult {\n    //Code to execute in background with return\n}.resultOnUI { result -\u003e\n\n}\n\nor\n\nrunOnBackgroundWithResult {\n    //Code to execute in background with return\n}.resultOnBackground { result -\u003e\n\n}\n```\n\n```kotlin\nclass PostViewModel(view: LoginView) : BaseViewModel\u003cLoginView\u003e(view) {\n\n    fun getPostsFromAPI() {\n\n        runOnBackgroundWithResult {\n            JsonPlaceHolderServiceAPI().getPosts(1)    //getPost returns data so return statement is not needed\n            //or \n            // return@runOnBackgroundWithResult JsonPlaceHolderServiceAPI().getPosts(1)\n        }.resultOnUI {\n            getView()?.showPopUpMessage(\n                \"First Post Details\",\n                \"Username : ${it.first().name}\\n email : ${it.first().email}\"\n            )\n        }\n    }\n\n    fun savePost() {\n\n        val post = PostModel(\"Post Body\", \"jit@ccc.com\", 100, \"Jitty\", 6)\n\n        runOnBackgroundWithResult {\n            JsonPlaceHolderServiceAPI().setPost(post)\n        }.resultOnUI {\n            getView()?.showPopUpMessage(\"Saved Post Details\", \"Name : ${it.name}\\n email : ${it.email}\")\n        }\n    }\n}\n```\n\n### 3. Multiplatform Bundle : Object Passing B/W Activities or ViewControllers\n\nView Model can pass objects \u0026 values from Activity to Activity (Android) or ViewController to ViewController (iOS)\n\n###### Send Values From 1st View Model\n\n```kotlin\n   // 1st View Model \n\nvar userModel = UserModel(\"jittya@gmail.com\", \"Jitty\", \"Andiyan\")\n\nvar bundle = Bundle {\n    putStringExtra(HomeViewModel.USER_NAME, username.toString())\n    putSerializableExtra(HomeViewModel.USER_OBJECT, userModel, UserModel.serializer())\n}\n\ngetView()?.navigateToHomePage(bundle)\n\n\n// 1st View \n\nfun navigateToHomePage(bundle: BundleX)\n\n\n// 1st Activity : Android\n\noverride fun navigateToHomePage(bundle: BundleX) {\n    openActivity(HomeActivity::class.java, bundle)\n    finish()\n}\n\n// 1st ViewContoller : iOS\n\nfunc navigateToHomePage (bundle: BundleX) {\n    openViewController(newViewControllerName: \"HomeViewController\", bundle: bundle)\n}\n\n```\n\n###### Retrieve Values From 2nd View Model\n\n```kotlin\n   // 2nd View Model \n\nclass HomeViewModel(view: HomeView) : BaseViewModel\u003cHomeView\u003e(view) {\n\n    companion object BundleKeys {\n        const val USER_NAME = \"USERNAME\"\n        const val USER_OBJECT = \"USEROBJ\"\n    }\n\n    override fun onStartViewModel() {\n\n        getBundleValue\u003cString\u003e(USER_NAME)?.let { username -\u003e\n\n        }\n        getBundleValue\u003cUserModel\u003e(USER_OBJECT)?.let { userModel -\u003e\n\n        }\n    }\n}\n```\n\n### 4. Platform Blocks\n\nExecute anything specific to a particular platform using Platform Blocks\n\n```kotlin\n\nrunOnAndroid {\n\n}\n\nrunOniOS {\n\n}\n\n```\n\n### 5. Object Serialization Helper ( [Kotlinx.Serialization] )\n\nUse **_toJsonString_** and **_toObject_** functions for instant serialization.\n\n_Objects to String Serialization_\n\n```koltin\n        var userModel = UserModel(\"jittya@gmail.com\", \"Jitty\", \"Andiyan\")\n        \n        var jsonString = userModel.toJsonString(UserModel.serializer())\n```\n\n_String to Object Serialization_\n\n```koltin\n        var userModel = jsonString.toObject\u003cUserModel\u003e()\n        \n        or\n        \n        var userModel:UserModel = jsonString.toObject()\n        \n        or\n        \n        var userModel = jsonString.toObject(UserModel.serializer())\n```\n\n### 6. Key Value Store ( [Multiplatform Settings] )\n\nUse **_storeValue_** and **_getStoreValue_** functions for storing and retrieving Key-Value respectively\n\n_Storing **Key-Value** pair_\n\n```koltin\n        var userModel = UserModel(\"jittya@gmail.com\", \"Jitty\", \"Andiyan\")\n        \n        storeValue { \n            putString(\"Key1\",\"Value\")\n            putBoolean(\"Key2\",false)\n            putSerializable(\"Key3\",userModel,UserModel.serializer())\n        }\n```\n\n_Retrieve **Value** using **Key**_\n\n```koltin\n        var stringValue = getStoreValue\u003cString\u003e(\"Key1\")\n        \n        or\n        \n        var stringValue:String? = getStoreValue(\"Key1\")\n        \n        var boolValue = getStoreValue\u003cBoolean\u003e(\"Key2\")\n        \n        var userModel = getStoreValue\u003cUserModel\u003e(\"Key3\",UserModel.serializer())\n```\n\n### 7. LiveData \u0026 LiveDataObservable ( [LiveData] )\n\nLiveData follows the observer pattern. LiveData notifies Observer objects when underlying data changes. You can\nconsolidate your code to update the UI in these Observer objects. That way, you don't need to update the UI every time\nthe app data changes because the observer does it for you.\n\n```koltin\n        //Sources\n        var premiumManager = PremiumManager()\n        var premiumManagerBoolean = PremiumManagerBoolean()\n\n        //Create Observer \u0026 Observe\n        var subscriptionLiveDataObservable = observe\u003cString\u003e {\n           getView()?.setSubscriptionLabel(it)\n        }\n        \n        //Adding Sources\n        subscriptionLiveDataObservable.addSource(premiumManager.premium())\n\n        or\n        \n        //Adding Sources with converter (Boolean to String)\n        subscriptionLiveDataObservable.addSource(premiumManagerBoolean.isPremium()){\n            if (it)\n            {\n                return@addSource \"Premium\"\n            }else{\n                return@addSource \"Free\"\n            }\n\n        }\n\n        //Update source states\n        premiumManager.becomePremium()\n\n        premiumManagerBoolean.becomeFree()\n\n        premiumManager.becomeFree()\n\n        premiumManagerBoolean.becomePremium()\n\n```\n\n```koltin\nclass PremiumManager {\n    private val premium = MutableLiveDataX\u003cString\u003e()\n    fun premium(): LiveDataX\u003cString\u003e {\n        return premium\n    }\n\n    fun becomePremium() {\n        premium.value = \"premium\"\n    }\n\n    fun becomeFree() {\n        premium.value = \"free\"\n    }\n\n}\n\nclass PremiumManagerBoolean {\n    private val premium = MutableLiveDataX\u003cBoolean\u003e()\n    fun isPremium(): LiveDataX\u003cBoolean\u003e {\n        return premium\n    }\n\n    fun becomePremium() {\n        premium.value = true\n    }\n\n    fun becomeFree() {\n        premium.value = false\n    }\n\n}\n```\n\n### 8. Observe with DBHelper ( Local Database : SQLite - [SQLDelight] )\n\nUse 'asFlow()' extension from DBHelper class to observe a query data\n\n```kotlin\nclass BreedTableHelper : DBHelper() {\n\n    fun getAllBreeds(): Flow\u003cList\u003cTBreed\u003e\u003e =\n        localDB.tBreedQueries\n            .selectAll()\n            .asFlow()\n            .mapToList()\n            .flowOn(Dispatchers_Default)\n\n\n    suspend fun insertBreeds(breeds: List\u003cTBreed\u003e) {\n        ...\n    }\n\n    fun selectById(id: Long): Flow\u003cList\u003cTBreed\u003e\u003e =\n        localDB.tBreedQueries\n            .selectById(id)\n            .asFlow()\n            .mapToList()\n            .flowOn(Dispatchers_Default)\n\n    suspend fun deleteAll() {\n        ...\n    }\n\n    suspend fun updateFavorite(breedId: Long, favorite: Boolean) {\n        localDB.transactionWithContext(Dispatchers_Default) {\n            localDB.tBreedQueries.updateFavorite(favorite, breedId)\n        }\n    }\n\n}\n```\n\n```kotlin\nclass BreedViewModel(view: BreedView) : BaseViewModel\u003cBreedView\u003e(view) {\n\n    private lateinit var breedTableHelper: BreedTableHelper\n    private lateinit var breedLiveDataObservable: LiveDataObservable\u003cEither\u003cList\u003cTBreed\u003e, Failure\u003e\u003e\n    private lateinit var breedListCache: BreedListCache\n\n    override fun onStartViewModel() {\n\n        breedTableHelper = BreedTableHelper()\n        breedListCache = BreedListCache(getBackgroundCoroutineScope())\n\n        breedLiveDataObservable = observe { breedList -\u003e\n            breedList.either({\n                getView()?.showPopUpMessage(it.message)\n                getView()?.stopRefreshing()\n            }, {\n                getView()?.refreshBreedList(it)\n                getView()?.stopRefreshing()\n            })\n\n        }\n\n        refreshBreedListCache(forceRefresh = false)\n\n        observeBreedsTable()\n\n    }\n\n    private fun observeBreedsTable() {\n        //get Data from db with observe (Flow)\n        runOnBackground {\n            //Each refreshBreedListCache will trigger collect \n            breedTableHelper.getAllBreeds().collect {\n                breedLiveDataObservable.setValue(Either.Success(it))\n            }\n        }\n    }\n\n    private fun refreshBreedListCache(forceRefresh: Boolean) {\n        breedListCache.cacheData(Unit, forceRefresh)\n        { cachedResult -\u003e\n            cachedResult.either({\n                breedLiveDataObservable.setValue(Either.Failure(it))\n            }, {\n                println(\"Cache Table updated : $it\")\n            })\n        }\n    }\n}\n```\n\n### 9. Useful Functional Programming\n\nuse *Either* data type to represent a value of one of two possible types (a disjoint union). Instances of *Either* are\neither an instance of *Failure* or *Success*\n\n```kotlin\n Either\u003cSuccessType, FailureType\u003e\n```\n\nconvert or map SuccessType using flatMap or map\n\n```kotlin\nvar result = doGet\u003cList\u003cUserModel\u003e\u003e {\n    apiPath(\"jittya/jsonserver/users?username=${credentails.username}\u0026password=${credentails.password}\")\n}\n\nreturn result.flatMap {\n    // convert List to Boolean\n    Either.Success(it.any { it.username == credentails.username \u0026\u0026 it.password == credentails.password })\n}\n```\n\nuse either blocks( *either* or *eitherAsync* [for suspended method support] ) to define failure \u0026 success\nfunctionalities\n\n```kotlin\n authenticatedResult.either({\n    //Failure\n    getView()?.showPopUpMessage(it.message)\n\n}, { isAuthenticated -\u003e\n    //Success\n    if (isAuthenticated) {\n\n        var userModel = UserModel(\"jittya@gmail.com\", \"Jitty\", \"Andiyan\")\n\n        var bundle = Bundle {\n            putStringExtra(HomeViewModel.USER_NAME, username.toString())\n            putSerializableExtra(HomeViewModel.USER_OBJECT, userModel, UserModel.serializer())\n        }\n\n        getView()?.navigateToHomePage(bundle)\n    } else {\n        getView()?.showPopUpMessage(\"Login Failed\")\n    }\n})\n```\n\n### 10. Data Cache Helper\n\nUse *BaseDataCache\u003cRequestParamType, DataType\u003e* to implement data caching (remote to local). call *cacheData* function\nto get and save data\n\n```kotlin\nclass BreedListCache(backgroundCoroutineScope: CoroutineScope) :\n    BaseDataCache\u003cUnit, List\u003cTBreed\u003e\u003e(backgroundCoroutineScope, \"BREED_SYNC_TIME\") {\n    \n    override suspend fun getData(param: Unit): Either\u003cList\u003cTBreed\u003e, Failure\u003e {\n        //get data from remote (using api)\n        return BreedServiceAPI().getBreeds()\n    }\n\n    override suspend fun saveData(data: List\u003cTBreed\u003e): Either\u003cBoolean, Failure\u003e {\n        //save remote data in Local database\n        return try {\n            BreedTableHelper().insertBreeds(data)\n            Either.Success(true)\n        } catch (e: Exception) {\n            Either.Failure(DataBaseFailure(e))\n        }\n    }\n}\n```\n\n```kotlin\nvar breedListCache = BreedListCache(getBackgroundCoroutineScope())\n\nprivate fun refreshBreedListCache(forceRefresh: Boolean) {\n    \n//    breedListCache.cacheData(Unit, forceRefresh)\n//                or\n    breedListCache.cacheData(Unit, forceRefresh)\n    { cachedResult -\u003e\n        cachedResult.either({ failure -\u003e\n            println(\"Cache failed : $failure\")\n        }, { success -\u003e\n            println(\"Cache updated : $success\")\n        })\n    }\n}\n```\n\n## How to use\n\n#### Shared Module (Business Logics \u0026 UI Binding Methods) :\n\n##### _Step 1 : Define View_\n\n- Create a View interface by extending from BaseView.\n- Define UI binding functions in View interface.\n\n```kotlin\ninterface LoginView : BaseView {\n\n    fun setLoginPageLabel(msg: String)\n    fun setUsernameLabel(usernameLabel: String)\n    fun setPasswordLabel(passwordLabel: String)\n    fun setLoginButtonLabel(loginLabel: String)\n\n    fun getEnteredUsername(): String\n    fun getEnteredPassword(): String\n\n    fun setLoginButtonClickAction(onLoginClick: KFunction0\u003cUnit\u003e)\n\n    fun navigateToHomePage(bundle: BundleX)\n}\n```\n\n##### _Step 2 : Define ViewModel_\n\n- Create a ViewModel class by extending from BaseViewModel with View as Type.\n- Define your business logic in ViewModel class.\n\n```kotlin\nclass LoginViewModel(view: LoginView) : BaseViewModel\u003cLoginView\u003e(view) {\n    override fun onStartViewModel() {\n        getView()?.setLoginPageLabel(\"Login : ${Platform().platform}\")\n        getView()?.setUsernameLabel(\"Enter Username\")\n        getView()?.setPasswordLabel(\"Enter Password\")\n        getView()?.setLoginButtonLabel(\"Login\")\n        getView()?.setLoginButtonClickAction(this::onLoginButtonClick)\n    }\n\n    fun onLoginButtonClick() {\n        getView()?.showLoading(\"authenticating...\")\n        val username = getView()?.getEnteredUsername()\n        val password = getView()?.getEnteredPassword()\n        checkValidation(username, password)\n    }\n\n    fun checkValidation(username: String?, password: String?) {\n        if (username.isNullOrBlank().not() \u0026\u0026 password.isNullOrBlank().not()) {\n            val credentials = CredentialsModel(username.toString(), password.toString())\n\n            runOnBackgroundWithResult {\n                JsonPlaceHolderServiceAPI().authenticate(credentials)\n            }.resultOnUI { authenticatedResult -\u003e\n                getView()?.dismissLoading()\n                authenticatedResult.either({\n                    getView()?.showPopUpMessage(it.message)\n                }, { isAuthenticated -\u003e\n                    if (isAuthenticated) {\n                        var bundle = Bundle {\n                            putStringExtra(HomeViewModel.USER_NAME, username.toString())\n                        }\n                        getView()?.navigateToHomePage(bundle)\n                    } else {\n                        getView()?.showPopUpMessage(\n                            \"Login Failed\"\n                        )\n                    }\n                })\n\n            }\n        } else {\n            getView()?.showPopUpMessage(\"Validation Failed\", \"Username or Password is empty\")\n        }\n    }\n}\n```\n\n#### Android Module UI Binding :\n\n##### _Step 3 : Define Android View_\n\n- Create new activity by extending from KMMActivity with ViewModel as Type.\n- Implement created View interface in activity.\n- Implement all necessary methods from View \u0026 KMMActivity.\n\nImplement **_LoginView \u0026 Bind UI Controls_**\n\n```kotlin\nclass LoginActivity : KMMActivity\u003cLoginViewModel, ActivityMainBinding\u003e(), LoginView {\n\n    //Generated Methods from KMMActivity based on LoginViewModel\n    override fun initializeViewModel(): LoginViewModel {\n        return LoginViewModel(this)\n    }\n\n    override fun viewBindingInflate(): ActivityMainBinding {\n        return ActivityMainBinding.inflate(layoutInflater)\n    }\n\n    //Generated Methods from LoginView\n    override fun setLoginPageLabel(msg: String) {\n        binding.textView.text = msg\n    }\n\n    override fun setUsernameLabel(usernameLabel: String) {\n        binding.usernameET.hint = usernameLabel\n    }\n\n    override fun setPasswordLabel(passwordLabel: String) {\n        binding.passwordET.hint = passwordLabel\n    }\n\n    override fun getEnteredUsername(): String {\n        return binding.usernameET.text.toString()\n    }\n\n    override fun getEnteredPassword(): String {\n        return binding.passwordET.text.toString()\n    }\n\n    override fun setLoginButtonClickAction(onLoginClick: KFunction0\u003cUnit\u003e) {\n        binding.loginBtn.setClickAction(onLoginClick)\n    }\n\n    override fun setLoginButtonLabel(loginLabel: String) {\n        binding.loginBtn.text = loginLabel\n    }\n\n    override fun navigateToHomePage(bundle: BundleX) {\n        openActivity(HomeActivity::class.java, bundle)\n        finish()\n    }\n}\n```\n\n#### iOS Module UI Binding (Xcode) :\n\n##### _Step 4 : Define iOS View_\n\n- Create new viewcontroller by extending from KMMUIViewController.\n- Implement created View interface in viewcontroller.\n- Implement all necessary methods from View \u0026 KMMUIViewController.\n\nImplement **_LoginView \u0026 Bind UI Controls_**\n\n```kotlin\nclass LoginViewController : KMMUIViewController, LoginView {\n\n    @IBOutlet weak\n    var usernameTF: UITextFieldX!\n    @IBOutlet weak\n    var passwordTF: UITextFieldX!\n    @IBOutlet weak\n    var textlabel: UILabel!\n    @IBOutlet weak\n    var loginBtn: UIButton!\n\n    override func viewDidLoad()\n    {\n        super.viewDidLoad()\n        // Do any additional setup after loading the view.\n    }\n\n    //Generated Methods from LoginView\n    func setLoginPageLabel(msg: String)\n    {\n        textlabel.text = msg\n    }\n\n    func setUsernameLabel(usernameLabel: String)\n    {\n        usernameTF.placeholder = usernameLabel\n    }\n\n    func setPasswordLabel(passwordLabel: String)\n    {\n        passwordTF.placeholder = passwordLabel\n    }\n\n    func getEnteredUsername() -\u003e String\n    {\n        usernameTF.errorMessage = \"\"\n        return usernameTF.text ?? \"\"\n    }\n\n    func getEnteredPassword() -\u003e String\n    {\n        return passwordTF.text ?? \"\"\n    }\n\n    func setLoginButtonClickAction(onLoginClick: @escaping() -\u003e KotlinUnit)\n    {\n        loginBtn.setClickAction(action: onLoginClick)\n    }\n\n    func setLoginButtonLabel(loginLabel: String)\n    {\n        loginBtn.setTitle(loginLabel, for: UIControl.State.normal)\n    }\n\n    //Generated Methods from KMMUIViewController\n    override func initializeViewModel() -\u003e BaseViewModel\u003cBaseView\u003e\n    {\n        return LoginViewModel(view: self).getViewModel()\n    }\n\n    func navigateToHomePage(bundle: BundleX)\n    {\n        openViewController(newViewControllerName: \"HomeViewController\", bundle: bundle)\n    }\n}\n```\n\n##### _Subscribe for upcoming details and features..._\n\n![Views](https://komarev.com/ghpvc/?username=jittya-kmmt)\n\n\n[//]: # (These are reference links used in the body of this note and get stripped out when the markdown processor does its job. There is no need to format nicely because it shouldn't be seen. Thanks SO - http://stackoverflow.com/questions/4823468/store-comments-in-markdown-syntax)\n\n[Ktor]: \u003chttps://github.com/ktorio/ktor\u003e\n\n[Kotlinx.Coroutines]: \u003chttps://github.com/Kotlin/kotlinx.coroutines\u003e\n\n[SQLDelight]:\u003chttps://github.com/cashapp/sqldelight\u003e\n\n[Realm]:\u003chttps://github.com/realm/realm-kotlin\u003e\n\n[kotlinx.serialization]:\u003chttps://github.com/Kotlin/kotlinx.serialization\u003e\n\n[Multiplatform Settings]:\u003chttps://github.com/russhwolf/multiplatform-settings\u003e\n\n[LiveData]:\u003chttps://github.com/florent37/Multiplatform-LiveData\u003e\n\n[KaMP Kit]:\u003chttps://github.com/touchlab/KaMPKit\u003e\n\n[Koin]:\u003chttps://github.com/InsertKoinIO/koin\u003e\n\n[MokoResources]:\u003chttps://github.com/icerockdev/moko-resources\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjittya%2FKMMT","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjittya%2FKMMT","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjittya%2FKMMT/lists"}