├── 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 | 
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 |
44 |
45 |
52 |
53 |
54 |
55 |
56 |
68 |
69 |
70 |
76 |
77 |
78 |
79 |
87 |
--------------------------------------------------------------------------------
/SharedCode/src/commonMain/kotlin/com/jarroyo/sharedcode/viewModel/github/GitHubViewModel.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.github
6 |
7 | import com.jarroyo.sharedcode.base.Response
8 | import com.jarroyo.sharedcode.db.User
9 | import com.jarroyo.sharedcode.di.KodeinInjector
10 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo
11 | import com.jarroyo.sharedcode.domain.usecase.github.getRepos.GetGitHubRepoListRequest
12 | import com.jarroyo.sharedcode.domain.usecase.github.getRepos.GetGitHubRepoListUseCase
13 | import com.jarroyo.sharedcode.domain.usecase.user.CreateUserRequest
14 | import com.jarroyo.sharedcode.domain.usecase.user.CreateUserUseCase
15 | import com.jarroyo.sharedcode.domain.usecase.user.GetUserListUseCase
16 | import dev.icerock.moko.mvvm.livedata.MutableLiveData
17 | import dev.icerock.moko.mvvm.viewmodel.ViewModel
18 | import kotlinx.coroutines.ExperimentalCoroutinesApi
19 | import kotlinx.coroutines.cancel
20 | import kotlinx.coroutines.launch
21 | import org.kodein.di.instance
22 |
23 | @ExperimentalCoroutinesApi
24 | class GitHubViewModel : ViewModel() {
25 |
26 | // LIVE DATA
27 | var mGetGitHubRepoListLiveData =
28 | MutableLiveData(LoadingGetGitHubRepoListState())
29 |
30 | var mGetUserListLiveData =
31 | MutableLiveData(LoadingGetUserListState())
32 |
33 | // USE CASE
34 | private val mGetGitHubRepoListUseCase by KodeinInjector.instance()
35 | private val mGetUserListUseCase by KodeinInjector.instance()
36 | private val mCreateUserUseCase by KodeinInjector.instance()
37 |
38 | /**
39 | * GET GITHUB REPO LIST
40 | */
41 | fun getGitHubRepoListMokko(username: String) {
42 | viewModelScope.launch() {
43 |
44 | mGetGitHubRepoListLiveData.postValue(LoadingGetGitHubRepoListState())
45 |
46 | val request = GetGitHubRepoListRequest(username)
47 | val response = mGetGitHubRepoListUseCase.execute(request)
48 | processGitHubRepoListResponse(response)
49 | }
50 | }
51 |
52 | /**
53 | * PROCCESS RESPONSE
54 | */
55 | fun processGitHubRepoListResponse(response: Response>) {
56 | if (response is Response.Success) {
57 | mGetGitHubRepoListLiveData.postValue(
58 | SuccessGetGitHubRepoListState(
59 | response
60 | )
61 | )
62 | } else if (response is Response.Error) {
63 | mGetGitHubRepoListLiveData.postValue(
64 | ErrorGetGitHubRepoListState(
65 | response
66 | )
67 | )
68 | }
69 | }
70 |
71 | fun getUserList() {
72 | viewModelScope.launch() {
73 |
74 | mGetUserListLiveData.postValue(LoadingGetUserListState())
75 |
76 | val response = mGetUserListUseCase.execute()
77 | processUserList(response)
78 | }
79 | }
80 |
81 | fun processUserList(response: Response>) {
82 | if (response is Response.Success) {
83 | mGetUserListLiveData.postValue(
84 | SuccessGetUserListState(
85 | response
86 | )
87 | )
88 | } else if (response is Response.Error) {
89 | mGetUserListLiveData.postValue(
90 | ErrorGetUserListState(
91 | response
92 | )
93 | )
94 | }
95 | }
96 |
97 | fun createUser(name: String) {
98 | viewModelScope.launch() {
99 |
100 | mGetUserListLiveData.postValue(LoadingGetUserListState())
101 |
102 | val response = mCreateUserUseCase.execute(CreateUserRequest(name))
103 | processUserList(response)
104 | }
105 | }
106 |
107 | override fun onCleared() {
108 | super.onCleared()
109 | viewModelScope.cancel()
110 | }
111 | }
--------------------------------------------------------------------------------
/SharedCode/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'org.jetbrains.kotlin.multiplatform'
2 | apply plugin: 'kotlinx-serialization'
3 | apply plugin: 'com.android.library'
4 | apply plugin: 'com.squareup.sqldelight'
5 |
6 |
7 | android {
8 | compileSdkVersion(28)
9 |
10 | defaultConfig {
11 | minSdkVersion(21)
12 | targetSdkVersion(28)
13 | }
14 |
15 | // By default the android gradle plugin expects to find the kotlin source files in
16 | // the folder `main` and the test in the folder `test`. This is to be able place
17 | // the source code files inside androidMain and androidTest folders
18 | sourceSets {
19 | main {
20 | manifest.srcFile 'src/androidMain/AndroidManifest.xml'
21 | java.srcDirs = ['src/androidMain/kotlin']
22 | res.srcDirs = ['src/androidMain/res']
23 | }
24 | test {
25 | java.srcDirs = ['src/androidTest/kotlin']
26 | res.srcDirs = ['src/androidTest/res']
27 | }
28 | }
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 |
35 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
36 | kotlinOptions {
37 | jvmTarget = "1.8"
38 | }
39 | }
40 | }
41 |
42 | kotlin {
43 | targets {
44 | fromPreset(presets.android, 'android')
45 |
46 |
47 | def iosPreset = presets.iosX64
48 | fromPreset(iosPreset, 'ios') {
49 | binaries {
50 | framework {
51 | // Disable bitcode embedding for the simulator build.
52 | if (iosPreset == presets.iosX64) {
53 | embedBitcode("disable")
54 | }
55 | }
56 | }
57 | //compilations.main.outputKinds('FRAMEWORK')
58 | compilations.each {
59 | //it.extraOpts("-linkerOpts", "-lsqlite3")
60 | }
61 | }
62 |
63 | }
64 |
65 | sourceSets {
66 | commonMain.dependencies {
67 | api 'org.jetbrains.kotlin:kotlin-stdlib-common'
68 |
69 | // COROUTINES
70 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
71 |
72 | // MOKO - MVVM
73 | implementation "dev.icerock.moko:mvvm:$moko_mvvm_version"
74 |
75 | // KODE IN
76 | implementation "org.kodein.di:kodein-di:$kodeinVersion"
77 |
78 | // KTOR
79 | implementation "io.ktor:ktor-client-core:$ktor_version"
80 | implementation("io.ktor:ktor-client-json:$ktor_version")
81 | implementation("io.ktor:ktor-client-logging:$ktor_version")
82 | implementation("io.ktor:ktor-client-serialization:$ktor_version")
83 |
84 | // SQL Delight
85 | implementation "com.squareup.sqldelight:runtime:$sql_delight_version"
86 | }
87 |
88 | androidMain.dependencies {
89 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
90 | implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
91 |
92 | // MOKO - MVVM
93 | implementation "androidx.lifecycle:lifecycle-extensions:$androidx_lifecycle_version"
94 |
95 | // KTOR
96 | implementation "io.ktor:ktor-client-android:$ktor_version"
97 |
98 | // SQL Delight
99 | implementation("com.squareup.sqldelight:android-driver:$sql_delight_version")
100 | }
101 |
102 | iosMain.dependencies {
103 | implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
104 |
105 | // KTOR
106 | implementation "io.ktor:ktor-client-ios:$ktor_version"
107 |
108 | // SQL Delight
109 | implementation("com.squareup.sqldelight:native-driver:$sql_delight_version")
110 | }
111 |
112 | commonTest.dependencies {
113 | implementation kotlin('test-common')
114 | implementation kotlin('test-annotations-common')
115 | implementation "io.mockk:mockk:$mockk_version"
116 |
117 | // COROUTINES
118 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutine_version"
119 |
120 |
121 | implementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlin_version"
122 | implementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
123 | }
124 | }
125 |
126 | sqldelight {
127 | AppDatabase {
128 | packageName = "com.jarroyo.sharedcode.db"
129 | }
130 | linkSqlite = true
131 | }
132 | }
133 |
134 | task packForXCode {
135 | def buildType = project.findProperty("kotlin.build.type") ?: "DEBUG"
136 | dependsOn "link${buildType.toLowerCase().capitalize()}FrameworkIos"
137 | //dependsOn "linkMainDebugFrameworkIOS"
138 |
139 | doLast {
140 | def srcFile = kotlin.targets.ios.binaries.getFramework(buildType).outputFile
141 | def targetDir = getProperty("configuration.build.dir")
142 | copy {
143 | from srcFile.parent
144 | into targetDir
145 | include 'SharedCode.framework/**'
146 | include 'SharedCode .framework.dSYM'
147 | }
148 | }
149 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/AndroidApp/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/iOSApp/iOSApp/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.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 | import SharedCode
11 |
12 | class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
13 | // View
14 | @IBOutlet weak var mButton: UIButton!
15 | @IBOutlet weak var mCounterLabel: UILabel!
16 | @IBOutlet weak var mTableView: UITableView!
17 |
18 | @IBOutlet weak var mDbTableView:UITableView!
19 | @IBOutlet weak var mButtonAddDatabase: UIButton!
20 | @IBOutlet weak var mEditTextUser: UITextField!
21 |
22 | private var mCounterViewModel: CounterViewModel!
23 | private var mGitHubViewModel: GitHubViewModel!
24 |
25 | // Table View Data
26 | internal var mGitHubRepoList: [GitHubRepo] = []
27 |
28 | override func viewDidLoad() {
29 | super.viewDidLoad()
30 | configView()
31 | initViewModel()
32 | }
33 |
34 | func configView() {
35 | mButton.addTarget(self, action: #selector(didButtonClick), for: .touchUpInside)
36 |
37 | mTableView.dataSource = self
38 | mTableView.delegate = self
39 |
40 | mDbTableView.dataSource = self
41 | mDbTableView.delegate = self
42 |
43 | mButtonAddDatabase.addTarget(self, action: #selector(didButtonAddDataBaseClick), for: .touchUpInside)
44 | }
45 |
46 | func initViewModel() {
47 | mCounterViewModel = CounterViewModel()
48 | mGitHubViewModel = GitHubViewModel()
49 | observeCounterViewModel()
50 | observeGitHubViewModel()
51 | observeUserListViewModel()
52 | }
53 |
54 | /****************************************************************************
55 | * OBSERVER VIEW MODEL
56 | ***************************************************************************/
57 | func observeCounterViewModel() {
58 | mCounterViewModel.mGetCounterLiveData.addObserver { (mCurrentState) in
59 | if (mCurrentState is SuccessGetCounterState) {
60 | let successState = mCurrentState as! SuccessGetCounterState
61 | let response = (successState.response as! ResponseSuccess)
62 | let value = response.data as! Int
63 | self.mCounterLabel.text = String(value)
64 |
65 | } else if (mCurrentState is LoadingGetCounterState) {
66 | self.mCounterLabel.text = "Loading"
67 | } else if (mCurrentState is ErrorGetCounterState) {
68 | self.mCounterLabel.text = "ERROR"
69 | }
70 |
71 | }
72 | }
73 |
74 | func observeGitHubViewModel() {
75 | mGitHubViewModel.mGetGitHubRepoListLiveData.addObserver { (mCurrentState) in
76 | if (mCurrentState is SuccessGetGitHubRepoListState) {
77 | let successState = mCurrentState as! SuccessGetGitHubRepoListState
78 | let response = (successState.response as! ResponseSuccess)
79 | let value = response.data as! [GitHubRepo]
80 | self.onSuccessGetGitHubRepoList(list: value)
81 |
82 | } else if (mCurrentState is LoadingGetGitHubRepoListState) {
83 | //self.mCounterLabel.text = "Loading"
84 | } else if (mCurrentState is ErrorGetGitHubRepoListState) {
85 | //self.mCounterLabel.text = "ERROR"
86 | }
87 |
88 | }
89 | }
90 |
91 | func observeUserListViewModel() {
92 | mGitHubViewModel.mGetUserListLiveData.addObserver { (mCurrentState) in
93 | if (mCurrentState is SuccessGetUserListState) {
94 | let successState = mCurrentState as! SuccessGetUserListState
95 | let response = (successState.response as! ResponseSuccess)
96 | let value = response.data as! [User]
97 | let lastUser = value.last?.name ?? "unknown"
98 | self.mCounterLabel.text = lastUser + String(value.count)
99 |
100 | } else if (mCurrentState is LoadingGetUserListState) {
101 | self.mCounterLabel.text = "Loading"
102 | } else if (mCurrentState is ErrorGetUserListState) {
103 | self.mCounterLabel.text = "ERROR"
104 | }
105 |
106 | }
107 | }
108 |
109 | func onSuccessGetGitHubRepoList(list: [GitHubRepo]) {
110 | update(list: list)
111 | }
112 |
113 | /****************************************************************************
114 | * ON CLICKS
115 | ****************************************************************************/
116 | @objc func didButtonClick(_ sender: UIButton) {
117 | mGitHubViewModel.getGitHubRepoListMokko(username: "jarroyoesp")
118 | }
119 |
120 | @objc func didButtonAddDataBaseClick(_ sender: UIButton) {
121 | var userString: String = self.mEditTextUser.text ?? "unknown"
122 | mGitHubViewModel.createUser(name: userString)
123 | }
124 |
125 | /*****************************************************************************
126 | TABLE VIEW
127 | ****************************************************************************/
128 | internal func update(list: [GitHubRepo]) {
129 | mGitHubRepoList.removeAll()
130 | mGitHubRepoList.append(contentsOf: list)
131 | mTableView.reloadData()
132 | }
133 |
134 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
135 | return mGitHubRepoList.count
136 | }
137 |
138 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
139 | let cell = tableView.dequeueReusableCell(withIdentifier: "locationListCell", for: indexPath)
140 | let entry = mGitHubRepoList[indexPath.row]
141 |
142 | cell.textLabel?.text = entry.name
143 | return cell
144 | }
145 |
146 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
147 | let entryNum = mGitHubRepoList[indexPath.row].name
148 | }
149 |
150 |
151 | deinit {
152 | mCounterViewModel.onCleared()
153 | mGitHubViewModel.onCleared()
154 | }
155 |
156 | }
157 |
158 |
159 |
--------------------------------------------------------------------------------
/AndroidApp/src/main/java/com/jarroyo/kmp_mvvm/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.jarroyo.kmp_mvvm
2 |
3 | import android.os.Bundle
4 | import android.widget.Toast
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.lifecycle.ViewModelProviders
7 | import androidx.recyclerview.widget.LinearLayoutManager
8 | import com.jarroyo.kmp_mvvm.ui.main.adapter.GitHubRepoRVAdapter
9 | import com.jarroyo.kmp_mvvm.ui.main.adapter.UserRVAdapter
10 | import com.jarroyo.sharedcode.base.Response
11 | import com.jarroyo.sharedcode.db.User
12 | import com.jarroyo.sharedcode.domain.model.github.GitHubRepo
13 | import com.jarroyo.sharedcode.platformName
14 | import com.jarroyo.sharedcode.viewModel.*
15 | import com.jarroyo.sharedcode.viewModel.github.*
16 | import kotlinx.android.synthetic.main.activity_main.*
17 |
18 | class MainActivity : AppCompatActivity() {
19 |
20 | // View Model
21 | lateinit var mGitHubViewModel: GitHubViewModel
22 |
23 | // RV Adapter
24 | private var mLayoutManager: LinearLayoutManager? = null
25 | private var mLayoutManagerUser: LinearLayoutManager? = null
26 | private var mRvAdapter: GitHubRepoRVAdapter? = null
27 | private var mUserRvAdapter: UserRVAdapter? = null
28 |
29 | private lateinit var getGithubListObserver : (state: GetGitHubRepoListState) -> Unit
30 | private lateinit var getUserListObserver : (state: GetUserListState) -> Unit
31 |
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | super.onCreate(savedInstanceState)
34 | setContentView(R.layout.activity_main)
35 |
36 | configView()
37 | initViewModel()
38 |
39 |
40 | }
41 |
42 | override fun onDestroy() {
43 | super.onDestroy()
44 | mGitHubViewModel.mGetGitHubRepoListLiveData.removeObserver(getGithubListObserver)
45 | }
46 |
47 | private fun configView() {
48 | activity_main_button.setOnClickListener {
49 | mGitHubViewModel.getGitHubRepoListMokko("jarroyoesp")
50 | mGitHubViewModel.getUserList()
51 | }
52 | activity_main_button_add.setOnClickListener {
53 | val name = activity_main_et_user.text.toString()
54 | if (!name.isNullOrEmpty()) {
55 | mGitHubViewModel.createUser(name)
56 | }
57 | }
58 | }
59 |
60 | private fun initViewModel() {
61 | mGitHubViewModel = ViewModelProviders.of(this).get(GitHubViewModel::class.java)
62 | observeViewModel()
63 | mGitHubViewModel.getUserList()
64 | }
65 |
66 | private fun configRecyclerView(treatmentList: List) {
67 | if (activity_main_rv.adapter == null) {
68 | mLayoutManager = LinearLayoutManager(
69 | this,
70 | LinearLayoutManager.VERTICAL, false
71 | )
72 | activity_main_rv.layoutManager = mLayoutManager
73 |
74 | mRvAdapter = GitHubRepoRVAdapter(treatmentList)
75 |
76 | activity_main_rv.adapter = mRvAdapter
77 |
78 | } else {
79 | mRvAdapter?.setList(treatmentList)
80 | mRvAdapter?.notifyDataSetChanged()
81 | }
82 | }
83 |
84 |
85 | private fun configRecyclerViewUser(userList: List) {
86 | if (activity_main_rv_users.adapter == null) {
87 | mLayoutManagerUser = LinearLayoutManager(
88 | this,
89 | LinearLayoutManager.VERTICAL, false
90 | )
91 | activity_main_rv_users.layoutManager = mLayoutManagerUser
92 |
93 | mUserRvAdapter = UserRVAdapter(userList)
94 |
95 | activity_main_rv_users.adapter = mUserRvAdapter
96 |
97 | } else {
98 | mUserRvAdapter?.setList(userList)
99 | mUserRvAdapter?.notifyDataSetChanged()
100 | }
101 | }
102 |
103 | /****************************************************************************
104 | * OBSERVER
105 | ***************************************************************************/
106 | private fun observeViewModel() {
107 | getGithubListObserver = { getGitHubListState(mGitHubViewModel.mGetGitHubRepoListLiveData.value) }
108 | getUserListObserver = { getUserListObserver(mGitHubViewModel.mGetUserListLiveData.value) }
109 |
110 | mGitHubViewModel.mGetGitHubRepoListLiveData.addObserver(getGithubListObserver)
111 | mGitHubViewModel.mGetUserListLiveData.addObserver(getUserListObserver)
112 | }
113 |
114 | fun getUserListObserver(state: GetUserListState) {
115 | when (state) {
116 | is SuccessGetUserListState -> {
117 | hideLoading()
118 | val response = state.response as Response.Success
119 | onSuccessUserList(response.data)
120 | }
121 |
122 | is LoadingGetUserListState -> {
123 | showLoading()
124 | }
125 |
126 | is ErrorGetUserListState -> {
127 | hideLoading()
128 | val response = state as Response.Error
129 | showError(response.message)
130 | }
131 | }
132 | }
133 |
134 | fun getGitHubListState(state: GetGitHubRepoListState) {
135 | when (state) {
136 | is SuccessGetGitHubRepoListState -> {
137 | hideLoading()
138 | val response = state.response as Response.Success
139 | onSuccessGetGitHubList(response.data)
140 | }
141 |
142 | is LoadingGetGitHubRepoListState -> {
143 | showLoading()
144 | }
145 |
146 | is ErrorGetGitHubRepoListState -> {
147 | hideLoading()
148 | val response = state.response as Response.Error
149 | showError(response.message)
150 | }
151 | }
152 | }
153 |
154 | /**
155 | * ON SUCCESS
156 | */
157 | private fun onSuccessUserList(userList: List) {
158 | configRecyclerViewUser(userList)
159 | }
160 |
161 | private fun onSuccessGetGitHubList(list: List) {
162 | configRecyclerView(list)
163 | }
164 |
165 | /**
166 | * SHOW LOADING
167 | */
168 | private fun showLoading() {
169 | Toast.makeText(this, "Loading...", Toast.LENGTH_SHORT).show()
170 | }
171 |
172 | /**
173 | * HIDE LOADING
174 | */
175 | private fun hideLoading() {
176 | }
177 |
178 | /**
179 | * SHOW ERROR
180 | */
181 | private fun showError(message: String?) {
182 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
183 | }
184 |
185 | }
186 |
--------------------------------------------------------------------------------
/iOSApp/iOSApp/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
26 |
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 |
--------------------------------------------------------------------------------