├── AndroidApp ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── item_rv_github_repo_list.xml │ │ │ │ └── activity_main.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── jarroyo │ │ │ │ └── kmp_mvvm │ │ │ │ ├── ui │ │ │ │ ├── App.kt │ │ │ │ └── main │ │ │ │ │ └── adapter │ │ │ │ │ ├── UserRVAdapter.kt │ │ │ │ │ └── GitHubRepoRVAdapter.kt │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── jarroyo │ │ │ └── kmp_mvvm │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── jarroyo │ │ └── kmp_mvvm │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── images ├── KMP_MVVM.pages └── KMP_MVVM_Schema.png ├── iOSApp ├── iOSApp │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── AppDelegate.swift │ └── ViewController.swift ├── iOSApp.xcodeproj │ ├── xcuserdata │ │ └── javierarroyo.xcuserdatad │ │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcuserdata │ │ │ └── javierarroyo.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── project.pbxproj ├── iOSAppTests │ ├── Info.plist │ └── iOSAppTests.swift └── iOSAppUITests │ ├── Info.plist │ └── iOSAppUITests.swift ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── SharedCode ├── src │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ │ └── com │ │ │ └── jarroyo │ │ │ └── sharedcode │ │ │ ├── Dispatcher.kt │ │ │ ├── common.kt │ │ │ ├── utils │ │ │ └── networkSystem │ │ │ │ └── NetwotkSystem.kt │ │ │ └── DatabaseDriverFactory.kt │ ├── commonMain │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── jarroyo │ │ │ │ └── sharedcode │ │ │ │ ├── domain │ │ │ │ ├── usecase │ │ │ │ │ ├── base │ │ │ │ │ │ ├── BaseRequest.kt │ │ │ │ │ │ └── BaseUseCase.kt │ │ │ │ │ ├── counter │ │ │ │ │ │ ├── GetCounterRequest.kt │ │ │ │ │ │ └── GetCounterUseCase.kt │ │ │ │ │ ├── user │ │ │ │ │ │ ├── CreateUserRequest.kt │ │ │ │ │ │ ├── GetUserListUseCase.kt │ │ │ │ │ │ └── CreateUserUseCase.kt │ │ │ │ │ └── github │ │ │ │ │ │ └── getRepos │ │ │ │ │ │ ├── GetGitHubRepoListRequest.kt │ │ │ │ │ │ └── GetGitHubRepoListUseCase.kt │ │ │ │ └── model │ │ │ │ │ └── github │ │ │ │ │ └── GitHubRepo.kt │ │ │ │ ├── Dispatcher.kt │ │ │ │ ├── utils │ │ │ │ ├── networkSystem │ │ │ │ │ └── NetwotkSystem.kt │ │ │ │ └── coroutines │ │ │ │ │ └── CoroutineExt.kt │ │ │ │ ├── common.kt │ │ │ │ ├── base │ │ │ │ ├── exception │ │ │ │ │ └── NetworkConnectionException.kt │ │ │ │ └── Response.kt │ │ │ │ ├── data │ │ │ │ ├── source │ │ │ │ │ ├── network │ │ │ │ │ │ ├── INetworkDataSource.kt │ │ │ │ │ │ ├── NetworkDataSource.kt │ │ │ │ │ │ └── GitHubApi.kt │ │ │ │ │ └── disk │ │ │ │ │ │ ├── IDiskDataSource.kt │ │ │ │ │ │ └── Database.kt │ │ │ │ └── repository │ │ │ │ │ ├── CounterRepository.kt │ │ │ │ │ └── GitHubRepository.kt │ │ │ │ ├── di │ │ │ │ ├── InjectorCommon.kt │ │ │ │ └── KodeinInjector.kt │ │ │ │ ├── viewModel │ │ │ │ ├── GetCounterState.kt │ │ │ │ ├── github │ │ │ │ │ ├── GetUserListState.kt │ │ │ │ │ ├── GetGitHubRepoListState.kt │ │ │ │ │ └── GitHubViewModel.kt │ │ │ │ └── CounterViewModel.kt │ │ │ │ └── DatabaseDriverFactory.kt │ │ └── sqldelight │ │ │ └── com │ │ │ └── jarroyo │ │ │ └── sharedcode │ │ │ └── db │ │ │ └── AppDatabase.sq │ ├── iOSMain │ │ └── kotlin │ │ │ └── com │ │ │ └── jarroyo │ │ │ └── sharedcode │ │ │ ├── utils.networkSystem │ │ │ └── NetwotkSystem.kt │ │ │ ├── common.kt │ │ │ ├── Dispatcher.kt │ │ │ └── DatabaseDriverFactory.kt │ └── commonTest │ │ └── kotlin │ │ └── com │ │ └── jarroyo │ │ └── sharedcode │ │ └── data │ │ └── repository │ │ └── GitHubRepositoryTest.kt ├── .gitignore └── build.gradle ├── .gitignore ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /AndroidApp/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /images/KMP_MVVM.pages: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/images/KMP_MVVM.pages -------------------------------------------------------------------------------- /images/KMP_MVVM_Schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/images/KMP_MVVM_Schema.png -------------------------------------------------------------------------------- /AndroidApp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | KMP_MVVM 3 | 4 | -------------------------------------------------------------------------------- /iOSApp/iOSApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':AndroidApp', ':SharedCode' 2 | rootProject.name='KMP_MVVM' 3 | 4 | enableFeaturePreview('GRADLE_METADATA') 5 | -------------------------------------------------------------------------------- /SharedCode/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /AndroidApp/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/AndroidApp/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidApp/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/AndroidApp/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidApp/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/AndroidApp/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/AndroidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/AndroidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/AndroidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /AndroidApp/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/AndroidApp/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /AndroidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/AndroidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /AndroidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/AndroidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /AndroidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/AndroidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /iOSApp/iOSApp.xcodeproj/xcuserdata/javierarroyo.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/domain/usecase/base/BaseRequest.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.domain.usecase.base 2 | 3 | interface BaseRequest { 4 | fun validate(): Boolean 5 | } 6 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/Dispatcher.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode 2 | 3 | import kotlin.coroutines.CoroutineContext 4 | 5 | internal expect val ApplicationDispatcher: CoroutineContext 6 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/utils/networkSystem/NetwotkSystem.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.utils.networkSystem 2 | 3 | 4 | expect class ContextArgs 5 | expect fun isNetworkAvailable(): Boolean -------------------------------------------------------------------------------- /iOSApp/iOSApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SharedCode/src/iOSMain/kotlin/com/jarroyo/sharedcode/utils.networkSystem/NetwotkSystem.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.utils.networkSystem 2 | 3 | actual class ContextArgs( 4 | ) 5 | 6 | actual fun isNetworkAvailable(): Boolean{ 7 | return true 8 | } -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/common.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode 2 | 3 | 4 | expect fun platformName(): String 5 | // Common 6 | @Suppress("UnusedPrivateMember") 7 | internal expect fun runTest(block: suspend () -> T) 8 | -------------------------------------------------------------------------------- /AndroidApp/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/base/exception/NetworkConnectionException.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.base.exception 2 | 3 | 4 | class NetworkConnectionException(messageStr: String? = "Internet connection required.") : Exception(messageStr) 5 | -------------------------------------------------------------------------------- /SharedCode/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml 3 | .gradle 4 | /local.properties 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /captures 13 | .externalNativeBuild -------------------------------------------------------------------------------- /SharedCode/src/androidMain/kotlin/com/jarroyo/sharedcode/Dispatcher.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlin.coroutines.CoroutineContext 5 | 6 | internal actual val ApplicationDispatcher: CoroutineContext = Dispatchers.Default -------------------------------------------------------------------------------- /iOSApp/iOSApp.xcodeproj/project.xcworkspace/xcuserdata/javierarroyo.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarroyoesp/KotlinMultiplatform_MVVM/HEAD/iOSApp/iOSApp.xcodeproj/project.xcworkspace/xcuserdata/javierarroyo.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Dec 10 09:33:02 CET 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip 7 | -------------------------------------------------------------------------------- /SharedCode/src/iOSMain/kotlin/com/jarroyo/sharedcode/common.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode 2 | 3 | import kotlinx.coroutines.runBlocking 4 | 5 | actual fun platformName(): String { 6 | return "iOS" 7 | } 8 | 9 | internal actual fun runTest(block: suspend () -> T) { 10 | runBlocking { block() } 11 | } -------------------------------------------------------------------------------- /AndroidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /AndroidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /iOSApp/iOSApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/domain/usecase/counter/GetCounterRequest.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.domain.usecase.counter 2 | 3 | import com.jarroyo.sharedcode.domain.usecase.base.BaseRequest 4 | 5 | class GetCounterRequest() : BaseRequest { 6 | override fun validate(): Boolean { 7 | return true 8 | } 9 | } -------------------------------------------------------------------------------- /SharedCode/src/commonMain/sqldelight/com/jarroyo/sharedcode/db/AppDatabase.sq: -------------------------------------------------------------------------------- 1 | 2 | CREATE TABLE User ( 3 | id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 4 | name TEXT NOT NULL 5 | ); 6 | 7 | insertUser: 8 | INSERT OR FAIL INTO User(name) 9 | VALUES(?); 10 | 11 | removeAllUser: 12 | DELETE FROM User; 13 | 14 | selectAllUser: 15 | SELECT * 16 | FROM User; -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/domain/usecase/user/CreateUserRequest.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.domain.usecase.user 2 | 3 | import com.jarroyo.sharedcode.domain.usecase.base.BaseRequest 4 | 5 | class CreateUserRequest(val userName: String) : BaseRequest { 6 | override fun validate(): Boolean { 7 | return true 8 | } 9 | } -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/domain/usecase/github/getRepos/GetGitHubRepoListRequest.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.domain.usecase.github.getRepos 2 | 3 | import com.jarroyo.sharedcode.domain.usecase.base.BaseRequest 4 | 5 | class GetGitHubRepoListRequest(val userName: String) : BaseRequest { 6 | override fun validate(): Boolean { 7 | return true 8 | } 9 | } -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/data/source/network/INetworkDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.data.source.network 2 | 3 | import com.jarroyo.sharedcode.base.Response 4 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo 5 | 6 | abstract class INetworkDataSource { 7 | 8 | abstract suspend fun getGitHubRepoList(userName: String): Response> 9 | } -------------------------------------------------------------------------------- /SharedCode/src/androidMain/kotlin/com/jarroyo/sharedcode/common.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode 2 | // Android 3 | import kotlinx.coroutines.ExperimentalCoroutinesApi 4 | import kotlinx.coroutines.runBlocking 5 | 6 | actual fun platformName(): String { 7 | return "Android" 8 | } 9 | 10 | 11 | 12 | @UseExperimental(ExperimentalCoroutinesApi::class) 13 | internal actual fun runTest(block: suspend () -> T) { 14 | runBlocking { block() } 15 | } -------------------------------------------------------------------------------- /AndroidApp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /AndroidApp/src/test/java/com/jarroyo/kmp_mvvm/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.kmp_mvvm 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /iOSApp/iOSApp.xcodeproj/xcuserdata/javierarroyo.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | iOSApp.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/di/InjectorCommon.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.di 2 | 3 | import com.jarroyo.sharedcode.utils.networkSystem.ContextArgs 4 | import kotlin.native.concurrent.ThreadLocal 5 | 6 | @ThreadLocal 7 | object InjectorCommon { 8 | 9 | var mContextArgs: ContextArgs? = null 10 | 11 | fun provideContextArgs(contextArgs: ContextArgs): ContextArgs? { 12 | mContextArgs = contextArgs 13 | return mContextArgs 14 | } 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/viewModel/GetCounterState.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.viewModel 2 | 3 | import com.jarroyo.sharedcode.base.Response 4 | 5 | 6 | sealed class GetCounterState { 7 | abstract val response: Response? 8 | } 9 | data class SuccessGetCounterState(override val response: Response) : GetCounterState() 10 | data class LoadingGetCounterState(override val response: Response? = null) : GetCounterState() 11 | data class ErrorGetCounterState(override val response: Response) : GetCounterState() -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/domain/usecase/counter/GetCounterUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.domain.usecase.counter 2 | 3 | import com.jarroyo.sharedcode.base.Response 4 | import com.jarroyo.sharedcode.data.repository.CounterRepository 5 | import com.jarroyo.sharedcode.domain.usecase.base.BaseUseCase 6 | 7 | 8 | open class GetCounterUseCase(val repository: CounterRepository) : BaseUseCase() { 9 | 10 | override suspend fun run(): Response { 11 | return repository.getCounter(request!!) 12 | } 13 | } -------------------------------------------------------------------------------- /AndroidApp/src/main/res/layout/item_rv_github_repo_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/domain/usecase/base/BaseUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.domain.usecase.base 2 | 3 | import com.jarroyo.sharedcode.base.Response 4 | 5 | abstract class BaseUseCase { 6 | 7 | protected var request: R? = null 8 | 9 | suspend fun execute(request: R? = null): Response { 10 | this.request = request 11 | 12 | val validated = request?.validate() ?: true 13 | if (validated) return run() 14 | return Response.Error(IllegalArgumentException()) 15 | } 16 | 17 | abstract suspend fun run(): Response 18 | } 19 | -------------------------------------------------------------------------------- /SharedCode/src/iOSMain/kotlin/com/jarroyo/sharedcode/Dispatcher.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode 2 | 3 | import kotlinx.coroutines.* 4 | import platform.darwin.* 5 | import kotlin.coroutines.CoroutineContext 6 | 7 | internal actual val ApplicationDispatcher: CoroutineContext = //Dispatchers.Main 8 | NsQueueDispatcher(dispatch_get_main_queue()) 9 | 10 | internal class NsQueueDispatcher( 11 | private val dispatchQueue: dispatch_queue_t 12 | ) : CoroutineDispatcher() { 13 | override fun dispatch(context: CoroutineContext, block: Runnable) { 14 | dispatch_async(dispatchQueue) { 15 | block.run() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/domain/usecase/user/GetUserListUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.domain.usecase.user 2 | 3 | import com.jarroyo.sharedcode.base.Response 4 | import com.jarroyo.sharedcode.data.repository.GitHubRepository 5 | import com.jarroyo.sharedcode.db.User 6 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo 7 | import com.jarroyo.sharedcode.domain.usecase.base.BaseUseCase 8 | 9 | 10 | open class GetUserListUseCase(val repository: GitHubRepository) : BaseUseCase>() { 11 | 12 | override suspend fun run(): Response> { 13 | return repository.getUserList() 14 | } 15 | } -------------------------------------------------------------------------------- /SharedCode/src/iOSMain/kotlin/com/jarroyo/sharedcode/DatabaseDriverFactory.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode 2 | 3 | import com.jarroyo.sharedcode.db.AppDatabase 4 | import com.jarroyo.sharedcode.utils.networkSystem.ContextArgs 5 | import com.squareup.sqldelight.db.SqlDriver 6 | import com.squareup.sqldelight.drivers.native.NativeSqliteDriver 7 | 8 | actual class DatabaseDriverFactory { 9 | actual fun createDriver(): SqlDriver { 10 | return NativeSqliteDriver(AppDatabase.Schema, "AppDatabase.db") 11 | } 12 | } 13 | 14 | actual fun getSqlDriver(contextArgs: ContextArgs?): SqlDriver { 15 | return NativeSqliteDriver(AppDatabase.Schema, "AppDatabase.db") 16 | } 17 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/base/Response.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.base 2 | 3 | sealed class Response { 4 | class Success(val data: T) : Response() 5 | data class Error(val exception: Throwable, 6 | val code: Int? = null, 7 | val error: Boolean? = null, 8 | val errors: List? = null, 9 | val message: String? = null, 10 | val method: String? = null, 11 | val path: String? = null) : Response() 12 | } 13 | 14 | data class ErrorX( 15 | val message: String, 16 | val path: String 17 | ) -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/domain/usecase/user/CreateUserUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.domain.usecase.user 2 | 3 | import com.jarroyo.sharedcode.base.Response 4 | import com.jarroyo.sharedcode.data.repository.GitHubRepository 5 | import com.jarroyo.sharedcode.db.User 6 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo 7 | import com.jarroyo.sharedcode.domain.usecase.base.BaseUseCase 8 | 9 | 10 | open class CreateUserUseCase(val repository: GitHubRepository) : BaseUseCase>() { 11 | 12 | override suspend fun run(): Response> { 13 | return repository.createUser(request) 14 | } 15 | } -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/domain/usecase/github/getRepos/GetGitHubRepoListUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.domain.usecase.github.getRepos 2 | 3 | import com.jarroyo.sharedcode.base.Response 4 | import com.jarroyo.sharedcode.data.repository.GitHubRepository 5 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo 6 | import com.jarroyo.sharedcode.domain.usecase.base.BaseUseCase 7 | 8 | 9 | open class GetGitHubRepoListUseCase(val repository: GitHubRepository) : BaseUseCase>() { 10 | 11 | override suspend fun run(): Response> { 12 | return repository.getRepos(request!!) 13 | } 14 | } -------------------------------------------------------------------------------- /SharedCode/src/androidMain/kotlin/com/jarroyo/sharedcode/utils/networkSystem/NetwotkSystem.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.utils.networkSystem 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import com.jarroyo.sharedcode.di.InjectorCommon 6 | 7 | actual class ContextArgs( 8 | var mContext: Context 9 | ) 10 | 11 | actual fun isNetworkAvailable(): Boolean{ 12 | val connectivityManager = InjectorCommon.mContextArgs?.mContext?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 13 | val activeNetworkInfo = connectivityManager.activeNetworkInfo 14 | return activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting 15 | 16 | } -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/viewModel/github/GetUserListState.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.viewModel.github 2 | 3 | import com.jarroyo.sharedcode.base.Response 4 | import com.jarroyo.sharedcode.db.User 5 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo 6 | 7 | 8 | sealed class GetUserListState { 9 | abstract val response: Response>? 10 | } 11 | data class SuccessGetUserListState(override val response: Response>) : GetUserListState() 12 | data class LoadingGetUserListState(override val response: Response>? = null) : GetUserListState() 13 | data class ErrorGetUserListState(override val response: Response>) : GetUserListState() -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/data/source/network/NetworkDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.data.source.network 2 | 3 | import com.jarroyo.kotlinmultiplatform.source.network.GitHubApi 4 | import com.jarroyo.sharedcode.base.Response 5 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo 6 | import io.ktor.client.* 7 | 8 | class NetworkDataSource(private val gitHubApi: GitHubApi, 9 | private val client: HttpClient): INetworkDataSource() { 10 | /** 11 | * GET GITHUB REPO LIST 12 | */ 13 | override suspend fun getGitHubRepoList(userName: String): Response> { 14 | return gitHubApi.getGitHubRepoList(client, userName) 15 | } 16 | } -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/viewModel/github/GetGitHubRepoListState.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.viewModel.github 2 | 3 | import com.jarroyo.sharedcode.base.Response 4 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo 5 | 6 | 7 | sealed class GetGitHubRepoListState { 8 | abstract val response: Response>? 9 | } 10 | data class SuccessGetGitHubRepoListState(override val response: Response>) : GetGitHubRepoListState() 11 | data class LoadingGetGitHubRepoListState(override val response: Response>? = null) : GetGitHubRepoListState() 12 | data class ErrorGetGitHubRepoListState(override val response: Response>) : GetGitHubRepoListState() -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/data/source/disk/IDiskDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.data.source.disk 2 | 3 | import com.jarroyo.sharedcode.base.Response 4 | import com.jarroyo.sharedcode.db.User 5 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo 6 | 7 | interface IDiskDataSource { 8 | suspend fun getUserList(): List 9 | suspend fun insertUser(name: String) 10 | } 11 | 12 | class DiskDataSourceImpl(private val database: Database?): IDiskDataSource { 13 | override suspend fun getUserList(): List { 14 | return database?.getAllUser() ?: emptyList() 15 | } 16 | 17 | override suspend fun insertUser(name: String) { 18 | database?.insertUser(name) 19 | } 20 | } -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/DatabaseDriverFactory.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode 2 | 3 | import com.jarroyo.sharedcode.data.source.disk.Database 4 | import com.jarroyo.sharedcode.utils.networkSystem.ContextArgs 5 | import com.squareup.sqldelight.db.SqlDriver 6 | 7 | 8 | expect class DatabaseDriverFactory { 9 | fun createDriver(): SqlDriver 10 | } 11 | 12 | expect fun getSqlDriver(contextArgs: ContextArgs? = null): SqlDriver 13 | 14 | object DatabaseCreator { 15 | fun getDataBase(contextArgs: ContextArgs?): Database? { 16 | val sqlDriver = getSqlDriver(contextArgs) 17 | if (sqlDriver != null) { 18 | return Database(sqlDriver) 19 | } else { 20 | return null 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SharedCode/src/androidMain/kotlin/com/jarroyo/sharedcode/DatabaseDriverFactory.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode 2 | 3 | import android.content.Context 4 | import com.squareup.sqldelight.android.AndroidSqliteDriver 5 | import com.squareup.sqldelight.db.SqlDriver 6 | import com.jarroyo.sharedcode.db.AppDatabase 7 | import com.jarroyo.sharedcode.utils.networkSystem.ContextArgs 8 | 9 | 10 | actual class DatabaseDriverFactory(private val context: Context) { 11 | actual fun createDriver(): SqlDriver { 12 | return AndroidSqliteDriver(AppDatabase.Schema, context, "AppDatabase.db") 13 | } 14 | } 15 | 16 | actual fun getSqlDriver(contextArgs: ContextArgs?): SqlDriver { 17 | return AndroidSqliteDriver(AppDatabase.Schema, contextArgs?.mContext!!, "AppDatabase.db") 18 | } 19 | -------------------------------------------------------------------------------- /iOSApp/iOSAppTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /iOSApp/iOSAppUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /AndroidApp/src/main/java/com/jarroyo/kmp_mvvm/ui/App.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.kmp_mvvm.ui 2 | 3 | import androidx.multidex.MultiDexApplication 4 | import com.jarroyo.sharedcode.di.InjectorCommon 5 | import com.jarroyo.sharedcode.utils.networkSystem.ContextArgs 6 | 7 | open class App : MultiDexApplication() { 8 | 9 | /* companion object { 10 | lateinit var graph: ApplicationComponent 11 | }*/ 12 | 13 | override fun onCreate() { 14 | super.onCreate() 15 | InjectorCommon.provideContextArgs(ContextArgs(this)) 16 | } 17 | 18 | /*private fun initializeDagger() { 19 | graph = DaggerApplicationComponent.builder() 20 | .applicationModule(ApplicationModule(this)) 21 | .build() 22 | 23 | InjectorCommon.provideContextArgs(ContextArgs(this)) 24 | }*/ 25 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | 16 | ## Build generated 17 | build/ 18 | DerivedData 19 | build.xcarchive 20 | 21 | ## Various settings 22 | *.pbxuser 23 | !default.pbxuser 24 | *.mode1v3 25 | !default.mode1v3 26 | *.mode2v3 27 | !default.mode2v3 28 | *.perspectivev3 29 | !default.perspectivev3 30 | xcuserdata 31 | 32 | ## Other 33 | *.xccheckout 34 | *.moved-aside 35 | *.xcuserstate 36 | *.xcscmblueprint 37 | 38 | ## Obj-C/Swift specific 39 | *.hmap 40 | *.ipa 41 | 42 | # CocoaPods 43 | Pods/ 44 | 45 | #Node Modules 46 | node_modules 47 | 48 | # Firebase 49 | AndroidApp/google-services.json -------------------------------------------------------------------------------- /AndroidApp/src/androidTest/java/com/jarroyo/kmp_mvvm/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.kmp_mvvm 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.jarroyo.kmp_mvvm", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /AndroidApp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/data/source/disk/Database.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.data.source.disk 2 | 3 | import com.jarroyo.sharedcode.DatabaseDriverFactory 4 | import com.jarroyo.sharedcode.db.AppDatabase 5 | import com.jarroyo.sharedcode.db.User 6 | import com.squareup.sqldelight.db.SqlDriver 7 | 8 | 9 | class Database(sqlDriver: SqlDriver) { 10 | private val database = AppDatabase(sqlDriver) 11 | private val dbQuery = database.appDatabaseQueries 12 | 13 | fun clearDatabase() { 14 | dbQuery.transaction { 15 | dbQuery.removeAllUser() 16 | } 17 | } 18 | 19 | fun getAllUser(): List { 20 | return dbQuery.selectAllUser().executeAsList() 21 | } 22 | 23 | fun insertUser(name: String) { 24 | dbQuery.insertUser( 25 | name = name 26 | ) 27 | } 28 | } -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/utils/coroutines/CoroutineExt.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.utils.coroutines 2 | 3 | import kotlinx.coroutines.* 4 | import kotlin.coroutines.CoroutineContext 5 | 6 | /** 7 | * Equivalent to [launch] but return [Unit] instead of [Job]. 8 | * 9 | * Mainly for usage when you want to lift [launch] to return. Example: 10 | * 11 | * ``` 12 | * override fun loadData() = launchSilent { 13 | * // code 14 | * } 15 | * ``` 16 | */ 17 | fun launchSilent( 18 | context: CoroutineContext = Dispatchers.Main, 19 | exceptionHandler: CoroutineExceptionHandler? = null, 20 | job: Job, 21 | start: CoroutineStart = CoroutineStart.DEFAULT, 22 | block: suspend CoroutineScope.() -> Unit 23 | ) { 24 | val coroutineScope = if (exceptionHandler != null) { 25 | CoroutineScope(context + job + exceptionHandler) 26 | } else { 27 | CoroutineScope(context + job) 28 | } 29 | coroutineScope.launch(context, start, block) 30 | } 31 | -------------------------------------------------------------------------------- /AndroidApp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /iOSApp/iOSAppTests/iOSAppTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iOSAppTests.swift 3 | // iOSAppTests 4 | // 5 | // Created by Javier Arroyo on 11/12/2019. 6 | // Copyright © 2019 Javier Arroyo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import iOSApp 11 | 12 | class iOSAppTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/data/repository/CounterRepository.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.data.repository 2 | 3 | import com.jarroyo.sharedcode.DatabaseCreator 4 | import com.jarroyo.sharedcode.base.Response 5 | import com.jarroyo.sharedcode.data.source.disk.Database 6 | import com.jarroyo.sharedcode.domain.usecase.counter.GetCounterRequest 7 | import com.jarroyo.sharedcode.utils.networkSystem.ContextArgs 8 | 9 | 10 | class CounterRepository( 11 | contextArgs: ContextArgs? 12 | ) { 13 | private var mDatabase: Database? = DatabaseCreator.getDataBase(contextArgs) 14 | /*********************************************************************************************** 15 | * GET COUNTER 16 | **********************************************************************************************/ 17 | suspend fun getCounter(request: GetCounterRequest): Response { 18 | mDatabase?.insertUser(name = "Jarroyoesp") 19 | val userList = mDatabase?.getAllUser() 20 | return Response.Success(userList?.last()?.id?.toInt() ?: -1) 21 | } 22 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /iOSApp/iOSAppUITests/iOSAppUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iOSAppUITests.swift 3 | // iOSAppUITests 4 | // 5 | // Created by Javier Arroyo on 11/12/2019. 6 | // Copyright © 2019 Javier Arroyo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class iOSAppUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 20 | XCUIApplication().launch() 21 | 22 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 23 | } 24 | 25 | override func tearDown() { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | } 28 | 29 | func testExample() { 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kotlin-Multiplatform MVVM (Android & iOS) 2 | ![kotlin-version](https://img.shields.io/badge/kotlin-1.5.30-orange) 3 | 4 | 5 | Example of application using Kotlin Multiplatform and MVVM pattern for both platforms (Android & iOS). To achieve it the libraries used are: 6 | 7 | - [moko-mvvm](https://github.com/icerockdev/moko-mvvm): This is a Kotlin Multiplatform library that provides architecture components of Model-View-ViewModel for UI applications. Components are lifecycle-aware on Android. 8 | - [KTOR](https://github.com/ktorio/ktor): to make HTTP requests 9 | - [Serialization](https://github.com/Kotlin/kotlinx.serialization): to De/Serializing JSON 10 | - [Kodein-DI](https://github.com/Kodein-Framework/Kodein-DI): Dependency injector 11 | - [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines): Library support for Kotlin coroutines with multiplatform support 12 | - [SQLDelight](https://github.com/cashapp/sqldelight): SQLDelight generates typesafe kotlin APIs from your SQL statements. 13 | 14 | More detail in this post on ProAndroidDev: [here](https://proandroiddev.com/kotlin-multiplatform-mvvm-clean-architecture-f20b99f90b95) 15 | 16 | ### Unit Testing SharedCode 17 | 18 | - Added unitTest for GitHubRepository 19 | 20 | 21 | -------------------------------------------------------------------------------- /iOSApp/iOSApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/data/source/network/GitHubApi.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.kotlinmultiplatform.source.network 2 | 3 | 4 | import com.jarroyo.sharedcode.base.Response 5 | import com.jarroyo.sharedcode.base.exception.NetworkConnectionException 6 | import io.ktor.client.features.json.serializer.KotlinxSerializer 7 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo 8 | import com.jarroyo.sharedcode.utils.networkSystem.isNetworkAvailable 9 | import io.ktor.client.HttpClient 10 | import io.ktor.client.features.json.JsonFeature 11 | import io.ktor.client.features.logging.DEFAULT 12 | import io.ktor.client.features.logging.LogLevel 13 | import io.ktor.client.features.logging.Logger 14 | import io.ktor.client.features.logging.Logging 15 | import io.ktor.client.request.get 16 | import kotlinx.serialization.json.Json 17 | 18 | class GitHubApi { 19 | 20 | suspend fun getGitHubRepoList(client: HttpClient, username: String): Response> { 21 | return try { 22 | if (isNetworkAvailable()) { 23 | 24 | Logger.DEFAULT.log("getGitHubRepoList - ") 25 | val url = "https://api.github.com/users/${username}/repos" 26 | val response = client.get>(url) 27 | Logger.DEFAULT.log("getGitHubRepoList - $response") 28 | Response.Success(response) 29 | } else { 30 | Response.Error(NetworkConnectionException()) 31 | } 32 | } catch (ex: Exception) { 33 | Logger.DEFAULT.log("getGitHubRepoList - "+ ex.message!!) 34 | Response.Error(ex) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /AndroidApp/src/main/java/com/jarroyo/kmp_mvvm/ui/main/adapter/UserRVAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.kmp_mvvm.ui.main.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.jarroyo.kmp_mvvm.R 8 | import com.jarroyo.sharedcode.db.User 9 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo 10 | import kotlinx.android.synthetic.main.item_rv_github_repo_list.view.* 11 | 12 | class UserRVAdapter( 13 | private var mList: List? = listOf() 14 | ) : RecyclerView.Adapter() { 15 | 16 | override fun getItemCount(): Int { 17 | return mList?.size ?: 0 18 | } 19 | 20 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 21 | val itemView = LayoutInflater.from(parent.context) 22 | .inflate(R.layout.item_rv_github_repo_list, parent, false) 23 | return UserViewHolder(itemView) 24 | 25 | } 26 | 27 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 28 | val viewHolder = holder as UserViewHolder 29 | viewHolder.bind( 30 | position, 31 | mList?.get(position) 32 | ) 33 | } 34 | 35 | class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 36 | fun bind( 37 | position: Int, 38 | user: User? 39 | ) = with(itemView) { 40 | item_rv_github_repo_list_tv.text = user?.name 41 | } 42 | } 43 | 44 | fun setList(list: List) { 45 | mList = list 46 | } 47 | 48 | data class Item(val position: Int, val gitHubRepo: GitHubRepo) 49 | } 50 | 51 | -------------------------------------------------------------------------------- /AndroidApp/src/main/java/com/jarroyo/kmp_mvvm/ui/main/adapter/GitHubRepoRVAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.kmp_mvvm.ui.main.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.jarroyo.kmp_mvvm.R 8 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo 9 | import kotlinx.android.synthetic.main.item_rv_github_repo_list.view.* 10 | 11 | class GitHubRepoRVAdapter( 12 | private var mList: List? = listOf() 13 | ) : RecyclerView.Adapter() { 14 | 15 | override fun getItemCount(): Int { 16 | return mList?.size ?: 0 17 | } 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 20 | val itemView = LayoutInflater.from(parent.context) 21 | .inflate(R.layout.item_rv_github_repo_list, parent, false) 22 | return GitHubViewHolder(itemView) 23 | 24 | } 25 | 26 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 27 | val viewHolder = holder as GitHubViewHolder 28 | viewHolder.bind( 29 | position, 30 | mList?.get(position) 31 | ) 32 | } 33 | 34 | class GitHubViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 35 | fun bind( 36 | position: Int, 37 | gitHubRepo: GitHubRepo? 38 | ) = with(itemView) { 39 | item_rv_github_repo_list_tv.text = gitHubRepo?.name 40 | } 41 | } 42 | 43 | fun setList(list: List) { 44 | mList = list 45 | } 46 | 47 | data class Item(val position: Int, val gitHubRepo: GitHubRepo) 48 | } 49 | 50 | -------------------------------------------------------------------------------- /iOSApp/iOSApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/viewModel/CounterViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.jarroyo.sharedcode.viewModel 6 | 7 | import com.jarroyo.sharedcode.base.Response 8 | import com.jarroyo.sharedcode.di.KodeinInjector 9 | import com.jarroyo.sharedcode.domain.usecase.counter.GetCounterRequest 10 | import com.jarroyo.sharedcode.domain.usecase.counter.GetCounterUseCase 11 | import dev.icerock.moko.mvvm.livedata.MutableLiveData 12 | import dev.icerock.moko.mvvm.viewmodel.ViewModel 13 | import kotlinx.coroutines.cancel 14 | import kotlinx.coroutines.launch 15 | import org.kodein.di.instance 16 | 17 | class CounterViewModel : ViewModel() { 18 | var mGetCounterLiveData = MutableLiveData(LoadingGetCounterState()) 19 | 20 | private val mGetCounterUseCase by KodeinInjector.instance() 21 | 22 | 23 | /** 24 | * GET COUNTER 25 | */ 26 | fun getCounter() = viewModelScope.launch { 27 | mGetCounterLiveData.postValue(LoadingGetCounterState()) 28 | 29 | //Logger.d("COUNTER VIEWMODEL", "my message") 30 | val request = GetCounterRequest() 31 | val response = mGetCounterUseCase.execute(request) 32 | processSaveUserResponse(response) 33 | } 34 | 35 | 36 | fun processSaveUserResponse(response: Response) { 37 | if (response is Response.Success) { 38 | mGetCounterLiveData.postValue( 39 | SuccessGetCounterState( 40 | response 41 | ) 42 | ) 43 | } else if (response is Response.Error) { 44 | mGetCounterLiveData.postValue( 45 | SuccessGetCounterState( 46 | response 47 | ) 48 | ) 49 | } 50 | } 51 | 52 | override fun onCleared() { 53 | super.onCleared() 54 | viewModelScope.cancel() 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /AndroidApp/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /iOSApp/iOSApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/data/repository/GitHubRepository.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.data.repository 2 | 3 | import com.jarroyo.sharedcode.base.Response 4 | import com.jarroyo.sharedcode.data.source.disk.IDiskDataSource 5 | import com.jarroyo.sharedcode.data.source.network.INetworkDataSource 6 | import com.jarroyo.sharedcode.db.User 7 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo 8 | import com.jarroyo.sharedcode.domain.usecase.github.getRepos.GetGitHubRepoListRequest 9 | import com.jarroyo.sharedcode.domain.usecase.user.CreateUserRequest 10 | 11 | 12 | class GitHubRepository( 13 | private val diskDataSource: IDiskDataSource, 14 | private val networkDataSource: INetworkDataSource 15 | ) { 16 | 17 | /*********************************************************************************************** 18 | * GET USER LIST 19 | **********************************************************************************************/ 20 | suspend fun getUserList(): Response> { 21 | return Response.Success(diskDataSource.getUserList()) 22 | } 23 | 24 | /*********************************************************************************************** 25 | * CREATE USER 26 | **********************************************************************************************/ 27 | suspend fun createUser(request: CreateUserRequest?): Response> { 28 | diskDataSource.insertUser(request?.userName ?: "Unknown") 29 | return Response.Success(diskDataSource.getUserList()) 30 | } 31 | 32 | /*********************************************************************************************** 33 | * GET REPOS 34 | **********************************************************************************************/ 35 | suspend fun getRepos(request: GetGitHubRepoListRequest): Response> { 36 | val userList = diskDataSource.getUserList() 37 | val response = networkDataSource.getGitHubRepoList(request.userName) 38 | return response 39 | } 40 | } -------------------------------------------------------------------------------- /AndroidApp/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "com.jarroyo.kmp_mvvm" 11 | minSdkVersion 21 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | packagingOptions { 25 | exclude("META-INF/*.kotlin_module") 26 | } 27 | 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | 33 | kotlinOptions { 34 | jvmTarget = "1.8" 35 | } 36 | } 37 | 38 | dependencies { 39 | implementation fileTree(dir: 'libs', include: ['*.jar']) 40 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 41 | implementation 'androidx.appcompat:appcompat:1.1.0' 42 | implementation 'androidx.core:core-ktx:1.0.2' 43 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 44 | testImplementation 'junit:junit:4.12' 45 | androidTestImplementation 'androidx.test.ext:junit:1.1.0' 46 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 47 | 48 | // SHARED CODE 49 | implementation project(':SharedCode') 50 | 51 | // ARCHITECTURE COMPONENTS 52 | implementation "android.arch.lifecycle:extensions:1.1.1" 53 | implementation "android.arch.lifecycle:viewmodel:1.1.1" 54 | 55 | implementation("dev.icerock.moko:mvvm:0.3.1") 56 | 57 | // MULTI DEX 58 | implementation 'androidx.multidex:multidex:2.0.0' 59 | androidTestImplementation('androidx.multidex:multidex-instrumentation:2.0.0') { 60 | exclude group: 'com.android.support', module: 'multidex' 61 | } 62 | 63 | // RECYCLER VIEW 64 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 65 | } 66 | -------------------------------------------------------------------------------- /iOSApp/iOSApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // iOSApp 4 | // 5 | // Created by Javier Arroyo on 11/12/2019. 6 | // Copyright © 2019 Javier Arroyo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SharedCode/src/commonTest/kotlin/com/jarroyo/sharedcode/data/repository/GitHubRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.data.repository 2 | 3 | import com.jarroyo.sharedcode.base.Response 4 | import com.jarroyo.sharedcode.base.exception.NetworkConnectionException 5 | import com.jarroyo.sharedcode.data.source.disk.DiskDataSourceImpl 6 | import com.jarroyo.sharedcode.data.source.disk.IDiskDataSource 7 | import com.jarroyo.sharedcode.data.source.network.INetworkDataSource 8 | import com.jarroyo.sharedcode.domain.usecase.github.getRepos.GetGitHubRepoListRequest 9 | import com.jarroyo.sharedcode.runTest 10 | import com.jarroyo.sharedcode.db.User 11 | import io.mockk.MockKAnnotations 12 | import io.mockk.impl.annotations.MockK 13 | import io.mockk.mockk 14 | import io.mockk.unmockkAll 15 | import io.mockk.coEvery 16 | import kotlinx.coroutines.Dispatchers 17 | import kotlinx.coroutines.GlobalScope 18 | import kotlinx.coroutines.withContext 19 | import kotlin.test.AfterTest 20 | import kotlin.test.BeforeTest 21 | import kotlin.test.Test 22 | import kotlin.test.assertEquals 23 | 24 | class GitHubRepositoryTest { 25 | @MockK 26 | lateinit var networkDataSource: INetworkDataSource 27 | @MockK 28 | lateinit var diskDataSource: IDiskDataSource 29 | 30 | lateinit var gitHubRepository: GitHubRepository 31 | 32 | @BeforeTest 33 | fun setUp() { 34 | MockKAnnotations.init(this) 35 | gitHubRepository = GitHubRepository(diskDataSource, networkDataSource) 36 | } 37 | 38 | @AfterTest 39 | fun tearDown() { 40 | unmockkAll() 41 | } 42 | 43 | @Test 44 | fun GIVEN_gitHub_username_WHEN_request_repo_list_THEN_return_responseSuccess() = runTest { 45 | coEvery { diskDataSource.getUserList() } returns listOf(User(1L, "user")) 46 | coEvery { networkDataSource.getGitHubRepoList(any()) } returns Response.Success(emptyList()) 47 | val request = GetGitHubRepoListRequest("GitHubUserName") 48 | 49 | val response = gitHubRepository.getRepos(request) 50 | 51 | assert(response is Response.Success) 52 | 53 | } 54 | 55 | @Test 56 | fun GIVEN_gitHub_username_WHEN_request_repo_list_THEN_return_responseError() = runTest { 57 | coEvery { diskDataSource.getUserList() } returns listOf(User(1L, "user")) 58 | coEvery { networkDataSource.getGitHubRepoList(any()) } returns Response.Error(NetworkConnectionException()) 59 | val request = GetGitHubRepoListRequest("GitHubUserName") 60 | 61 | val response = gitHubRepository.getRepos(request) 62 | 63 | assert(response is Response.Error) 64 | 65 | } 66 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/di/KodeinInjector.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.di 2 | 3 | import com.jarroyo.kotlinmultiplatform.source.network.GitHubApi 4 | import com.jarroyo.sharedcode.ApplicationDispatcher 5 | import com.jarroyo.sharedcode.DatabaseCreator 6 | import com.jarroyo.sharedcode.data.repository.CounterRepository 7 | import com.jarroyo.sharedcode.data.repository.GitHubRepository 8 | import com.jarroyo.sharedcode.data.source.disk.DiskDataSourceImpl 9 | import com.jarroyo.sharedcode.data.source.disk.IDiskDataSource 10 | import com.jarroyo.sharedcode.data.source.network.INetworkDataSource 11 | import com.jarroyo.sharedcode.data.source.network.NetworkDataSource 12 | import com.jarroyo.sharedcode.domain.usecase.counter.GetCounterUseCase 13 | import com.jarroyo.sharedcode.domain.usecase.github.getRepos.GetGitHubRepoListUseCase 14 | import com.jarroyo.sharedcode.domain.usecase.user.CreateUserUseCase 15 | import com.jarroyo.sharedcode.domain.usecase.user.GetUserListUseCase 16 | import io.ktor.client.HttpClient 17 | import io.ktor.client.features.json.JsonFeature 18 | import io.ktor.client.features.json.serializer.KotlinxSerializer 19 | import kotlinx.coroutines.Dispatchers 20 | import kotlinx.serialization.json.Json 21 | import org.kodein.di.* 22 | import kotlin.coroutines.CoroutineContext 23 | import kotlin.native.concurrent.ThreadLocal 24 | 25 | @ThreadLocal 26 | val KodeinInjector = DI { 27 | 28 | //bind() with provider { ApplicationDispatcher } 29 | 30 | bind() with provider { Dispatchers.Main } 31 | 32 | val client = HttpClient() { 33 | install(JsonFeature) { 34 | serializer = KotlinxSerializer(json = Json { 35 | isLenient = false 36 | ignoreUnknownKeys = true 37 | allowSpecialFloatingPointValues = true 38 | useArrayPolymorphism = false 39 | }) 40 | } 41 | } 42 | 43 | /** 44 | * NETWORK API 45 | */ 46 | bind() with provider { GitHubApi() } 47 | 48 | /** 49 | * NETWORK DATA SOURCE 50 | */ 51 | bind() with provider { NetworkDataSource(instance(), client) } 52 | /** 53 | * Disk Data Source 54 | */ 55 | bind() with provider { DiskDataSourceImpl(DatabaseCreator.getDataBase(InjectorCommon.mContextArgs)) } 56 | 57 | 58 | /** 59 | * REPOSITORIES 60 | */ 61 | bind() with provider { CounterRepository(InjectorCommon.mContextArgs) } 62 | bind() with provider { GitHubRepository(instance(), instance()) } 63 | 64 | 65 | /** 66 | * USECASES 67 | */ 68 | bind() with singleton { GetCounterUseCase(instance()) } 69 | bind() with singleton { GetGitHubRepoListUseCase(instance()) } 70 | bind() with singleton { GetUserListUseCase(instance()) } 71 | bind() with singleton { CreateUserUseCase(instance()) } 72 | } -------------------------------------------------------------------------------- /SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/domain/model/github/GitHubRepo.kt: -------------------------------------------------------------------------------- 1 | package com.jarroyo.sharedcode.domain.model.github 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlinx.serialization.Transient 5 | 6 | @Serializable 7 | data class GitHubRepo( 8 | val archive_url: String, 9 | val archived: Boolean, 10 | val assignees_url: String, 11 | val blobs_url: String, 12 | val branches_url: String, 13 | val clone_url: String, 14 | val collaborators_url: String, 15 | val comments_url: String, 16 | val commits_url: String, 17 | val compare_url: String, 18 | val contents_url: String, 19 | val contributors_url: String, 20 | val created_at: String, 21 | val default_branch: String, 22 | val deployments_url: String, 23 | val disabled: Boolean, 24 | val downloads_url: String, 25 | val events_url: String, 26 | val fork: Boolean, 27 | val forks: Int, 28 | val forks_count: Int, 29 | val forks_url: String, 30 | val full_name: String, 31 | val git_commits_url: String, 32 | val git_refs_url: String, 33 | val git_tags_url: String, 34 | val git_url: String, 35 | val has_downloads: Boolean, 36 | val has_issues: Boolean, 37 | val has_pages: Boolean, 38 | val has_projects: Boolean, 39 | val has_wiki: Boolean, 40 | val hooks_url: String, 41 | val html_url: String, 42 | val id: Int, 43 | val issue_comment_url: String, 44 | val issue_events_url: String, 45 | val issues_url: String, 46 | val keys_url: String, 47 | val labels_url: String, 48 | val languages_url: String, 49 | val merges_url: String, 50 | val milestones_url: String, 51 | val name: String, 52 | val node_id: String, 53 | val notifications_url: String, 54 | val open_issues: Int, 55 | val open_issues_count: Int, 56 | val owner: Owner, 57 | val `private`: Boolean, 58 | val pulls_url: String, 59 | val pushed_at: String, 60 | val releases_url: String, 61 | val size: Int, 62 | val ssh_url: String, 63 | val stargazers_count: Int, 64 | val stargazers_url: String, 65 | val statuses_url: String, 66 | val subscribers_url: String, 67 | val subscription_url: String, 68 | val svn_url: String, 69 | val tags_url: String, 70 | val teams_url: String, 71 | val trees_url: String, 72 | val updated_at: String, 73 | val url: String, 74 | val watchers: Int, 75 | val watchers_count: Int 76 | ) { 77 | @Transient 78 | val license: License? = null 79 | 80 | 81 | @Transient 82 | val homepage: String? = null 83 | 84 | @Transient 85 | val description: String? = null 86 | @Transient 87 | val language: String? = null 88 | } 89 | @Serializable 90 | data class License( 91 | val key: String, 92 | val name: String, 93 | val node_id: String, 94 | val spdx_id: String, 95 | val url: String 96 | ) 97 | @Serializable 98 | data class Owner( 99 | val avatar_url: String, 100 | val events_url: String, 101 | val followers_url: String, 102 | val following_url: String, 103 | val gists_url: String, 104 | val gravatar_id: String, 105 | val html_url: String, 106 | val id: Int, 107 | val login: String, 108 | val node_id: String, 109 | val organizations_url: String, 110 | val received_events_url: String, 111 | val repos_url: String, 112 | val site_admin: Boolean, 113 | val starred_url: String, 114 | val subscriptions_url: String, 115 | val type: String, 116 | val url: String 117 | ) -------------------------------------------------------------------------------- /AndroidApp/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 18 | 19 | 29 | 30 | 36 | 37 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /iOSApp/iOSApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 04475FBD23A0EB5700D4A7D4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04475FBC23A0EB5700D4A7D4 /* AppDelegate.swift */; }; 11 | 04475FBF23A0EB5700D4A7D4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04475FBE23A0EB5700D4A7D4 /* ViewController.swift */; }; 12 | 04475FC223A0EB5700D4A7D4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 04475FC023A0EB5700D4A7D4 /* Main.storyboard */; }; 13 | 04475FC423A0EB5800D4A7D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 04475FC323A0EB5800D4A7D4 /* Assets.xcassets */; }; 14 | 04475FC723A0EB5800D4A7D4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 04475FC523A0EB5800D4A7D4 /* LaunchScreen.storyboard */; }; 15 | 04475FD223A0EB5800D4A7D4 /* iOSAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04475FD123A0EB5800D4A7D4 /* iOSAppTests.swift */; }; 16 | 04475FDD23A0EB5800D4A7D4 /* iOSAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04475FDC23A0EB5800D4A7D4 /* iOSAppUITests.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 04475FCE23A0EB5800D4A7D4 /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 04475FB123A0EB5700D4A7D4 /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = 04475FB823A0EB5700D4A7D4; 25 | remoteInfo = iOSApp; 26 | }; 27 | 04475FD923A0EB5800D4A7D4 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 04475FB123A0EB5700D4A7D4 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 04475FB823A0EB5700D4A7D4; 32 | remoteInfo = iOSApp; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 04475FB923A0EB5700D4A7D4 /* iOSApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 04475FBC23A0EB5700D4A7D4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | 04475FBE23A0EB5700D4A7D4 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 40 | 04475FC123A0EB5700D4A7D4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 41 | 04475FC323A0EB5800D4A7D4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 42 | 04475FC623A0EB5800D4A7D4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 43 | 04475FC823A0EB5800D4A7D4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | 04475FCD23A0EB5800D4A7D4 /* iOSAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 04475FD123A0EB5800D4A7D4 /* iOSAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSAppTests.swift; sourceTree = ""; }; 46 | 04475FD323A0EB5800D4A7D4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | 04475FD823A0EB5800D4A7D4 /* iOSAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 04475FDC23A0EB5800D4A7D4 /* iOSAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSAppUITests.swift; sourceTree = ""; }; 49 | 04475FDE23A0EB5800D4A7D4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 04475FB623A0EB5700D4A7D4 /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | 04475FCA23A0EB5800D4A7D4 /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | ); 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | 04475FD523A0EB5800D4A7D4 /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | 04475FB023A0EB5700D4A7D4 = { 78 | isa = PBXGroup; 79 | children = ( 80 | 04475FBB23A0EB5700D4A7D4 /* iOSApp */, 81 | 04475FD023A0EB5800D4A7D4 /* iOSAppTests */, 82 | 04475FDB23A0EB5800D4A7D4 /* iOSAppUITests */, 83 | 04475FBA23A0EB5700D4A7D4 /* Products */, 84 | ); 85 | sourceTree = ""; 86 | }; 87 | 04475FBA23A0EB5700D4A7D4 /* Products */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 04475FB923A0EB5700D4A7D4 /* iOSApp.app */, 91 | 04475FCD23A0EB5800D4A7D4 /* iOSAppTests.xctest */, 92 | 04475FD823A0EB5800D4A7D4 /* iOSAppUITests.xctest */, 93 | ); 94 | name = Products; 95 | sourceTree = ""; 96 | }; 97 | 04475FBB23A0EB5700D4A7D4 /* iOSApp */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 04475FBC23A0EB5700D4A7D4 /* AppDelegate.swift */, 101 | 04475FBE23A0EB5700D4A7D4 /* ViewController.swift */, 102 | 04475FC023A0EB5700D4A7D4 /* Main.storyboard */, 103 | 04475FC323A0EB5800D4A7D4 /* Assets.xcassets */, 104 | 04475FC523A0EB5800D4A7D4 /* LaunchScreen.storyboard */, 105 | 04475FC823A0EB5800D4A7D4 /* Info.plist */, 106 | ); 107 | path = iOSApp; 108 | sourceTree = ""; 109 | }; 110 | 04475FD023A0EB5800D4A7D4 /* iOSAppTests */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 04475FD123A0EB5800D4A7D4 /* iOSAppTests.swift */, 114 | 04475FD323A0EB5800D4A7D4 /* Info.plist */, 115 | ); 116 | path = iOSAppTests; 117 | sourceTree = ""; 118 | }; 119 | 04475FDB23A0EB5800D4A7D4 /* iOSAppUITests */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 04475FDC23A0EB5800D4A7D4 /* iOSAppUITests.swift */, 123 | 04475FDE23A0EB5800D4A7D4 /* Info.plist */, 124 | ); 125 | path = iOSAppUITests; 126 | sourceTree = ""; 127 | }; 128 | /* End PBXGroup section */ 129 | 130 | /* Begin PBXNativeTarget section */ 131 | 04475FB823A0EB5700D4A7D4 /* iOSApp */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = 04475FE123A0EB5800D4A7D4 /* Build configuration list for PBXNativeTarget "iOSApp" */; 134 | buildPhases = ( 135 | 04475FEA23A0EB6900D4A7D4 /* ShellScript */, 136 | 04475FB523A0EB5700D4A7D4 /* Sources */, 137 | 04475FB623A0EB5700D4A7D4 /* Frameworks */, 138 | 04475FB723A0EB5700D4A7D4 /* Resources */, 139 | ); 140 | buildRules = ( 141 | ); 142 | dependencies = ( 143 | ); 144 | name = iOSApp; 145 | productName = iOSApp; 146 | productReference = 04475FB923A0EB5700D4A7D4 /* iOSApp.app */; 147 | productType = "com.apple.product-type.application"; 148 | }; 149 | 04475FCC23A0EB5800D4A7D4 /* iOSAppTests */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = 04475FE423A0EB5800D4A7D4 /* Build configuration list for PBXNativeTarget "iOSAppTests" */; 152 | buildPhases = ( 153 | 04475FC923A0EB5800D4A7D4 /* Sources */, 154 | 04475FCA23A0EB5800D4A7D4 /* Frameworks */, 155 | 04475FCB23A0EB5800D4A7D4 /* Resources */, 156 | ); 157 | buildRules = ( 158 | ); 159 | dependencies = ( 160 | 04475FCF23A0EB5800D4A7D4 /* PBXTargetDependency */, 161 | ); 162 | name = iOSAppTests; 163 | productName = iOSAppTests; 164 | productReference = 04475FCD23A0EB5800D4A7D4 /* iOSAppTests.xctest */; 165 | productType = "com.apple.product-type.bundle.unit-test"; 166 | }; 167 | 04475FD723A0EB5800D4A7D4 /* iOSAppUITests */ = { 168 | isa = PBXNativeTarget; 169 | buildConfigurationList = 04475FE723A0EB5800D4A7D4 /* Build configuration list for PBXNativeTarget "iOSAppUITests" */; 170 | buildPhases = ( 171 | 04475FD423A0EB5800D4A7D4 /* Sources */, 172 | 04475FD523A0EB5800D4A7D4 /* Frameworks */, 173 | 04475FD623A0EB5800D4A7D4 /* Resources */, 174 | ); 175 | buildRules = ( 176 | ); 177 | dependencies = ( 178 | 04475FDA23A0EB5800D4A7D4 /* PBXTargetDependency */, 179 | ); 180 | name = iOSAppUITests; 181 | productName = iOSAppUITests; 182 | productReference = 04475FD823A0EB5800D4A7D4 /* iOSAppUITests.xctest */; 183 | productType = "com.apple.product-type.bundle.ui-testing"; 184 | }; 185 | /* End PBXNativeTarget section */ 186 | 187 | /* Begin PBXProject section */ 188 | 04475FB123A0EB5700D4A7D4 /* Project object */ = { 189 | isa = PBXProject; 190 | attributes = { 191 | LastSwiftUpdateCheck = 1020; 192 | LastUpgradeCheck = 1020; 193 | ORGANIZATIONNAME = "Javier Arroyo"; 194 | TargetAttributes = { 195 | 04475FB823A0EB5700D4A7D4 = { 196 | CreatedOnToolsVersion = 10.2.1; 197 | }; 198 | 04475FCC23A0EB5800D4A7D4 = { 199 | CreatedOnToolsVersion = 10.2.1; 200 | TestTargetID = 04475FB823A0EB5700D4A7D4; 201 | }; 202 | 04475FD723A0EB5800D4A7D4 = { 203 | CreatedOnToolsVersion = 10.2.1; 204 | TestTargetID = 04475FB823A0EB5700D4A7D4; 205 | }; 206 | }; 207 | }; 208 | buildConfigurationList = 04475FB423A0EB5700D4A7D4 /* Build configuration list for PBXProject "iOSApp" */; 209 | compatibilityVersion = "Xcode 9.3"; 210 | developmentRegion = en; 211 | hasScannedForEncodings = 0; 212 | knownRegions = ( 213 | en, 214 | Base, 215 | ); 216 | mainGroup = 04475FB023A0EB5700D4A7D4; 217 | productRefGroup = 04475FBA23A0EB5700D4A7D4 /* Products */; 218 | projectDirPath = ""; 219 | projectRoot = ""; 220 | targets = ( 221 | 04475FB823A0EB5700D4A7D4 /* iOSApp */, 222 | 04475FCC23A0EB5800D4A7D4 /* iOSAppTests */, 223 | 04475FD723A0EB5800D4A7D4 /* iOSAppUITests */, 224 | ); 225 | }; 226 | /* End PBXProject section */ 227 | 228 | /* Begin PBXResourcesBuildPhase section */ 229 | 04475FB723A0EB5700D4A7D4 /* Resources */ = { 230 | isa = PBXResourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | 04475FC723A0EB5800D4A7D4 /* LaunchScreen.storyboard in Resources */, 234 | 04475FC423A0EB5800D4A7D4 /* Assets.xcassets in Resources */, 235 | 04475FC223A0EB5700D4A7D4 /* Main.storyboard in Resources */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | 04475FCB23A0EB5800D4A7D4 /* Resources */ = { 240 | isa = PBXResourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | ); 244 | runOnlyForDeploymentPostprocessing = 0; 245 | }; 246 | 04475FD623A0EB5800D4A7D4 /* Resources */ = { 247 | isa = PBXResourcesBuildPhase; 248 | buildActionMask = 2147483647; 249 | files = ( 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | /* End PBXResourcesBuildPhase section */ 254 | 255 | /* Begin PBXShellScriptBuildPhase section */ 256 | 04475FEA23A0EB6900D4A7D4 /* ShellScript */ = { 257 | isa = PBXShellScriptBuildPhase; 258 | buildActionMask = 2147483647; 259 | files = ( 260 | ); 261 | inputFileListPaths = ( 262 | ); 263 | inputPaths = ( 264 | ); 265 | outputFileListPaths = ( 266 | ); 267 | outputPaths = ( 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | shellPath = /bin/sh; 271 | shellScript = "\"$SRCROOT/../gradlew\" -p \"$SRCROOT/../SharedCode\" packForXCode \\\n-Pconfiguration.build.dir=\"$CONFIGURATION_BUILD_DIR\" \\\n-Pkotlin.build.type=\"$KOTLIN_BUILD_TYPE\" \\\n-Pdevice=\"$KOTLIN_DEVICE\"\n"; 272 | }; 273 | /* End PBXShellScriptBuildPhase section */ 274 | 275 | /* Begin PBXSourcesBuildPhase section */ 276 | 04475FB523A0EB5700D4A7D4 /* Sources */ = { 277 | isa = PBXSourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | 04475FBF23A0EB5700D4A7D4 /* ViewController.swift in Sources */, 281 | 04475FBD23A0EB5700D4A7D4 /* AppDelegate.swift in Sources */, 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | 04475FC923A0EB5800D4A7D4 /* Sources */ = { 286 | isa = PBXSourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | 04475FD223A0EB5800D4A7D4 /* iOSAppTests.swift in Sources */, 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | }; 293 | 04475FD423A0EB5800D4A7D4 /* Sources */ = { 294 | isa = PBXSourcesBuildPhase; 295 | buildActionMask = 2147483647; 296 | files = ( 297 | 04475FDD23A0EB5800D4A7D4 /* iOSAppUITests.swift in Sources */, 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | /* End PBXSourcesBuildPhase section */ 302 | 303 | /* Begin PBXTargetDependency section */ 304 | 04475FCF23A0EB5800D4A7D4 /* PBXTargetDependency */ = { 305 | isa = PBXTargetDependency; 306 | target = 04475FB823A0EB5700D4A7D4 /* iOSApp */; 307 | targetProxy = 04475FCE23A0EB5800D4A7D4 /* PBXContainerItemProxy */; 308 | }; 309 | 04475FDA23A0EB5800D4A7D4 /* PBXTargetDependency */ = { 310 | isa = PBXTargetDependency; 311 | target = 04475FB823A0EB5700D4A7D4 /* iOSApp */; 312 | targetProxy = 04475FD923A0EB5800D4A7D4 /* PBXContainerItemProxy */; 313 | }; 314 | /* End PBXTargetDependency section */ 315 | 316 | /* Begin PBXVariantGroup section */ 317 | 04475FC023A0EB5700D4A7D4 /* Main.storyboard */ = { 318 | isa = PBXVariantGroup; 319 | children = ( 320 | 04475FC123A0EB5700D4A7D4 /* Base */, 321 | ); 322 | name = Main.storyboard; 323 | sourceTree = ""; 324 | }; 325 | 04475FC523A0EB5800D4A7D4 /* LaunchScreen.storyboard */ = { 326 | isa = PBXVariantGroup; 327 | children = ( 328 | 04475FC623A0EB5800D4A7D4 /* Base */, 329 | ); 330 | name = LaunchScreen.storyboard; 331 | sourceTree = ""; 332 | }; 333 | /* End PBXVariantGroup section */ 334 | 335 | /* Begin XCBuildConfiguration section */ 336 | 04475FDF23A0EB5800D4A7D4 /* Debug */ = { 337 | isa = XCBuildConfiguration; 338 | buildSettings = { 339 | ALWAYS_SEARCH_USER_PATHS = NO; 340 | CLANG_ANALYZER_NONNULL = YES; 341 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 342 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 343 | CLANG_CXX_LIBRARY = "libc++"; 344 | CLANG_ENABLE_MODULES = YES; 345 | CLANG_ENABLE_OBJC_ARC = YES; 346 | CLANG_ENABLE_OBJC_WEAK = YES; 347 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 348 | CLANG_WARN_BOOL_CONVERSION = YES; 349 | CLANG_WARN_COMMA = YES; 350 | CLANG_WARN_CONSTANT_CONVERSION = YES; 351 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 352 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 353 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 354 | CLANG_WARN_EMPTY_BODY = YES; 355 | CLANG_WARN_ENUM_CONVERSION = YES; 356 | CLANG_WARN_INFINITE_RECURSION = YES; 357 | CLANG_WARN_INT_CONVERSION = YES; 358 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 359 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 360 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 361 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 362 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 363 | CLANG_WARN_STRICT_PROTOTYPES = YES; 364 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 365 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 366 | CLANG_WARN_UNREACHABLE_CODE = YES; 367 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 368 | CODE_SIGN_IDENTITY = "iPhone Developer"; 369 | COPY_PHASE_STRIP = NO; 370 | DEBUG_INFORMATION_FORMAT = dwarf; 371 | ENABLE_STRICT_OBJC_MSGSEND = YES; 372 | ENABLE_TESTABILITY = YES; 373 | GCC_C_LANGUAGE_STANDARD = gnu11; 374 | GCC_DYNAMIC_NO_PIC = NO; 375 | GCC_NO_COMMON_BLOCKS = YES; 376 | GCC_OPTIMIZATION_LEVEL = 0; 377 | GCC_PREPROCESSOR_DEFINITIONS = ( 378 | "DEBUG=1", 379 | "$(inherited)", 380 | ); 381 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 382 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 383 | GCC_WARN_UNDECLARED_SELECTOR = YES; 384 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 385 | GCC_WARN_UNUSED_FUNCTION = YES; 386 | GCC_WARN_UNUSED_VARIABLE = YES; 387 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 388 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 389 | MTL_FAST_MATH = YES; 390 | ONLY_ACTIVE_ARCH = YES; 391 | SDKROOT = iphoneos; 392 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 393 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 394 | }; 395 | name = Debug; 396 | }; 397 | 04475FE023A0EB5800D4A7D4 /* Release */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | ALWAYS_SEARCH_USER_PATHS = NO; 401 | CLANG_ANALYZER_NONNULL = YES; 402 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 403 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 404 | CLANG_CXX_LIBRARY = "libc++"; 405 | CLANG_ENABLE_MODULES = YES; 406 | CLANG_ENABLE_OBJC_ARC = YES; 407 | CLANG_ENABLE_OBJC_WEAK = YES; 408 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 409 | CLANG_WARN_BOOL_CONVERSION = YES; 410 | CLANG_WARN_COMMA = YES; 411 | CLANG_WARN_CONSTANT_CONVERSION = YES; 412 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 413 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 414 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 415 | CLANG_WARN_EMPTY_BODY = YES; 416 | CLANG_WARN_ENUM_CONVERSION = YES; 417 | CLANG_WARN_INFINITE_RECURSION = YES; 418 | CLANG_WARN_INT_CONVERSION = YES; 419 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 420 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 421 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 422 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 423 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 424 | CLANG_WARN_STRICT_PROTOTYPES = YES; 425 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 426 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 427 | CLANG_WARN_UNREACHABLE_CODE = YES; 428 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 429 | CODE_SIGN_IDENTITY = "iPhone Developer"; 430 | COPY_PHASE_STRIP = NO; 431 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 432 | ENABLE_NS_ASSERTIONS = NO; 433 | ENABLE_STRICT_OBJC_MSGSEND = YES; 434 | GCC_C_LANGUAGE_STANDARD = gnu11; 435 | GCC_NO_COMMON_BLOCKS = YES; 436 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 437 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 438 | GCC_WARN_UNDECLARED_SELECTOR = YES; 439 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 440 | GCC_WARN_UNUSED_FUNCTION = YES; 441 | GCC_WARN_UNUSED_VARIABLE = YES; 442 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 443 | MTL_ENABLE_DEBUG_INFO = NO; 444 | MTL_FAST_MATH = YES; 445 | SDKROOT = iphoneos; 446 | SWIFT_COMPILATION_MODE = wholemodule; 447 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 448 | VALIDATE_PRODUCT = YES; 449 | }; 450 | name = Release; 451 | }; 452 | 04475FE223A0EB5800D4A7D4 /* Debug */ = { 453 | isa = XCBuildConfiguration; 454 | buildSettings = { 455 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 456 | CODE_SIGN_STYLE = Automatic; 457 | INFOPLIST_FILE = iOSApp/Info.plist; 458 | LD_RUNPATH_SEARCH_PATHS = ( 459 | "$(inherited)", 460 | "@executable_path/Frameworks", 461 | ); 462 | PRODUCT_BUNDLE_IDENTIFIER = com.jarroyo.iOSApp; 463 | PRODUCT_NAME = "$(TARGET_NAME)"; 464 | SWIFT_VERSION = 5.0; 465 | TARGETED_DEVICE_FAMILY = "1,2"; 466 | }; 467 | name = Debug; 468 | }; 469 | 04475FE323A0EB5800D4A7D4 /* Release */ = { 470 | isa = XCBuildConfiguration; 471 | buildSettings = { 472 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 473 | CODE_SIGN_STYLE = Automatic; 474 | INFOPLIST_FILE = iOSApp/Info.plist; 475 | LD_RUNPATH_SEARCH_PATHS = ( 476 | "$(inherited)", 477 | "@executable_path/Frameworks", 478 | ); 479 | PRODUCT_BUNDLE_IDENTIFIER = com.jarroyo.iOSApp; 480 | PRODUCT_NAME = "$(TARGET_NAME)"; 481 | SWIFT_VERSION = 5.0; 482 | TARGETED_DEVICE_FAMILY = "1,2"; 483 | }; 484 | name = Release; 485 | }; 486 | 04475FE523A0EB5800D4A7D4 /* Debug */ = { 487 | isa = XCBuildConfiguration; 488 | buildSettings = { 489 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 490 | BUNDLE_LOADER = "$(TEST_HOST)"; 491 | CODE_SIGN_STYLE = Automatic; 492 | INFOPLIST_FILE = iOSAppTests/Info.plist; 493 | LD_RUNPATH_SEARCH_PATHS = ( 494 | "$(inherited)", 495 | "@executable_path/Frameworks", 496 | "@loader_path/Frameworks", 497 | ); 498 | PRODUCT_BUNDLE_IDENTIFIER = com.jarroyo.iOSAppTests; 499 | PRODUCT_NAME = "$(TARGET_NAME)"; 500 | SWIFT_VERSION = 5.0; 501 | TARGETED_DEVICE_FAMILY = "1,2"; 502 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSApp.app/iOSApp"; 503 | }; 504 | name = Debug; 505 | }; 506 | 04475FE623A0EB5800D4A7D4 /* Release */ = { 507 | isa = XCBuildConfiguration; 508 | buildSettings = { 509 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 510 | BUNDLE_LOADER = "$(TEST_HOST)"; 511 | CODE_SIGN_STYLE = Automatic; 512 | INFOPLIST_FILE = iOSAppTests/Info.plist; 513 | LD_RUNPATH_SEARCH_PATHS = ( 514 | "$(inherited)", 515 | "@executable_path/Frameworks", 516 | "@loader_path/Frameworks", 517 | ); 518 | PRODUCT_BUNDLE_IDENTIFIER = com.jarroyo.iOSAppTests; 519 | PRODUCT_NAME = "$(TARGET_NAME)"; 520 | SWIFT_VERSION = 5.0; 521 | TARGETED_DEVICE_FAMILY = "1,2"; 522 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSApp.app/iOSApp"; 523 | }; 524 | name = Release; 525 | }; 526 | 04475FE823A0EB5800D4A7D4 /* Debug */ = { 527 | isa = XCBuildConfiguration; 528 | buildSettings = { 529 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 530 | CODE_SIGN_STYLE = Automatic; 531 | INFOPLIST_FILE = iOSAppUITests/Info.plist; 532 | LD_RUNPATH_SEARCH_PATHS = ( 533 | "$(inherited)", 534 | "@executable_path/Frameworks", 535 | "@loader_path/Frameworks", 536 | ); 537 | PRODUCT_BUNDLE_IDENTIFIER = com.jarroyo.iOSAppUITests; 538 | PRODUCT_NAME = "$(TARGET_NAME)"; 539 | SWIFT_VERSION = 5.0; 540 | TARGETED_DEVICE_FAMILY = "1,2"; 541 | TEST_TARGET_NAME = iOSApp; 542 | }; 543 | name = Debug; 544 | }; 545 | 04475FE923A0EB5800D4A7D4 /* Release */ = { 546 | isa = XCBuildConfiguration; 547 | buildSettings = { 548 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 549 | CODE_SIGN_STYLE = Automatic; 550 | INFOPLIST_FILE = iOSAppUITests/Info.plist; 551 | LD_RUNPATH_SEARCH_PATHS = ( 552 | "$(inherited)", 553 | "@executable_path/Frameworks", 554 | "@loader_path/Frameworks", 555 | ); 556 | PRODUCT_BUNDLE_IDENTIFIER = com.jarroyo.iOSAppUITests; 557 | PRODUCT_NAME = "$(TARGET_NAME)"; 558 | SWIFT_VERSION = 5.0; 559 | TARGETED_DEVICE_FAMILY = "1,2"; 560 | TEST_TARGET_NAME = iOSApp; 561 | }; 562 | name = Release; 563 | }; 564 | /* End XCBuildConfiguration section */ 565 | 566 | /* Begin XCConfigurationList section */ 567 | 04475FB423A0EB5700D4A7D4 /* Build configuration list for PBXProject "iOSApp" */ = { 568 | isa = XCConfigurationList; 569 | buildConfigurations = ( 570 | 04475FDF23A0EB5800D4A7D4 /* Debug */, 571 | 04475FE023A0EB5800D4A7D4 /* Release */, 572 | ); 573 | defaultConfigurationIsVisible = 0; 574 | defaultConfigurationName = Release; 575 | }; 576 | 04475FE123A0EB5800D4A7D4 /* Build configuration list for PBXNativeTarget "iOSApp" */ = { 577 | isa = XCConfigurationList; 578 | buildConfigurations = ( 579 | 04475FE223A0EB5800D4A7D4 /* Debug */, 580 | 04475FE323A0EB5800D4A7D4 /* Release */, 581 | ); 582 | defaultConfigurationIsVisible = 0; 583 | defaultConfigurationName = Release; 584 | }; 585 | 04475FE423A0EB5800D4A7D4 /* Build configuration list for PBXNativeTarget "iOSAppTests" */ = { 586 | isa = XCConfigurationList; 587 | buildConfigurations = ( 588 | 04475FE523A0EB5800D4A7D4 /* Debug */, 589 | 04475FE623A0EB5800D4A7D4 /* Release */, 590 | ); 591 | defaultConfigurationIsVisible = 0; 592 | defaultConfigurationName = Release; 593 | }; 594 | 04475FE723A0EB5800D4A7D4 /* Build configuration list for PBXNativeTarget "iOSAppUITests" */ = { 595 | isa = XCConfigurationList; 596 | buildConfigurations = ( 597 | 04475FE823A0EB5800D4A7D4 /* Debug */, 598 | 04475FE923A0EB5800D4A7D4 /* Release */, 599 | ); 600 | defaultConfigurationIsVisible = 0; 601 | defaultConfigurationName = Release; 602 | }; 603 | /* End XCConfigurationList section */ 604 | }; 605 | rootObject = 04475FB123A0EB5700D4A7D4 /* Project object */; 606 | } 607 | --------------------------------------------------------------------------------