├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── androidApp ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── simform │ │ └── kmmsample │ │ └── androidApp │ │ ├── base │ │ └── Resource.kt │ │ ├── ui │ │ ├── MainActivity.kt │ │ ├── MainViewModel.kt │ │ └── MovieListAdapter.kt │ │ └── utils │ │ ├── ImageUtils.kt │ │ ├── MutableLiveDataExtension.kt │ │ └── binding.kt │ └── res │ ├── drawable │ ├── placeholder.jpg │ └── tmdb.xml │ ├── layout │ ├── activity_main.xml │ └── movie_list.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── java │ ├── App.kt │ ├── Extensions.kt │ ├── Libs.kt │ ├── Plugins.kt │ ├── SharedLibs.kt │ ├── SharedPlugins.kt │ ├── SharedVersions.kt │ ├── Urls.kt │ └── Versions.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── iosApp ├── Podfile ├── iosApp.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── iosApp │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── placeHolderImage.imageset │ │ │ ├── Contents.json │ │ │ └── placeholder.jpg │ ├── Colors.xcassets │ │ ├── Contents.json │ │ ├── darkGrey.colorset │ │ │ └── Contents.json │ │ ├── mainColor.colorset │ │ │ └── Contents.json │ │ └── navigationBarColor.colorset │ │ │ └── Contents.json │ ├── Constants │ │ └── Structures.swift │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── Storyboards │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ └── Main.storyboard │ └── ViewControllers │ │ ├── MainViewController.swift │ │ ├── MainViewModel.swift │ │ └── MoviesCell.swift ├── iosAppTests │ ├── Info.plist │ └── iosAppTests.swift └── iosAppUITests │ ├── Info.plist │ └── iosAppUITests.swift ├── screenshots ├── KMM Plugin.png ├── Run Configurations.png ├── android.gif └── iOS.gif ├── settings.gradle.kts └── shared ├── build.gradle.kts └── src ├── androidMain ├── AndroidManifest.xml └── kotlin │ └── com │ └── simform │ └── kmmsample │ └── shared │ └── HttpBaseClient.kt ├── commonMain └── kotlin │ └── com │ └── simform │ └── kmmsample │ └── shared │ ├── HttpBaseClient.kt │ ├── datamodels │ ├── base │ │ ├── CustomException.kt │ │ ├── Either.kt │ │ └── ErrorResponse.kt │ └── responsemodels │ │ ├── MovieEnitity.kt │ │ └── PopularMoviesResponse.kt │ ├── remote │ └── BaseApiClass.kt │ └── utils │ ├── ApiEndPoints.kt │ └── SharedConstants.kt └── iosMain └── kotlin └── com └── simform └── kmmsample └── shared └── HttpBaseClient.kt /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS or Android] 28 | - Version [e.g. 1.1] 29 | 30 | **Smartphone (please complete the following information):** 31 | - Device: [e.g. iPhone6] 32 | - OS: [e.g. iOS8.1] 33 | - Browser [e.g. stock browser, safari] 34 | - Version [e.g. 1.1] 35 | 36 | **Additional context** 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/* 17 | **/.idea/* 18 | !.idea/fileTemplates/ 19 | 20 | /build/ 21 | 22 | # Additional 23 | Gemfile.lock 24 | android/*.jks 25 | android/jks/*.jks 26 | android/keystore.properties 27 | drafts/ 28 | logs/ 29 | 30 | #Android git ignore 31 | **/**/androidApp/build/ 32 | local.properties 33 | .gradle 34 | build/ 35 | **/**/androidApp/build 36 | **/**/androidApp/captures 37 | **/**/androidApp/.externalNativeBuild 38 | **/**/androidApp/report.xml 39 | settings/reports 40 | lint/intermediates/ 41 | lint/generated/ 42 | lint/outputs/ 43 | lint/tmp/ 44 | 45 | 46 | #Common gitignore 47 | shared/build 48 | 49 | #iOS git ignore 50 | *.perspectivev3 51 | !default.perspectivev3 52 | ## Other 53 | *.moved-aside 54 | *.xccheckout 55 | *.xcscmblueprint 56 | .DS_Store 57 | ## Obj-C/Swift specific 58 | **/**/iosApp/Pods 59 | **/**/iosApp/*.app.dSYM.zip 60 | **/**/iosApp/*.ipa 61 | **/**/iosApp/*.xcuserdata 62 | **/**/iosApp/*.xcworkspace/contents.xcworkspacedata 63 | **/**/iosApp/*DerivedData/ 64 | **/**/iosApp/*.moved-aside 65 | **/**/iosApp/*.xccheckout 66 | **/**/iosApp/*.xcscmblueprint 67 | **/**/iosApp/.DS_Store 68 | **/**/iosApp/Podfile.lock 69 | **/**/iosApp/iosApp/LocalizeFile/R.generated.swift 70 | **/**/iosApp/**/xcuserdata/*.* 71 | **/**/iosApp/*.xcworkspace/contents.xcworkspacedata/ 72 | **/**/iosApp/iosApp.xcworkspace/xcshareddata 73 | 74 | ## Playgrounds 75 | timeline.xctimeline 76 | playground.xcworkspace 77 | # Swift Package Manager 78 | # 79 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 80 | # Packages/ 81 | # Package.pins 82 | # Package.resolved 83 | .build/ 84 | # CocoaPods 85 | # 86 | # We recommend against adding the Pods directory to your .gitignore. However 87 | # you should judge for yourself, the pros and cons are mentioned at: 88 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 89 | # 90 | **/**/iosApp/Pods/ 91 | /.DS_Store 92 | # Carthage 93 | # 94 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 95 | # Carthage/Checkouts 96 | **/**/iosApp/Carthage/Build 97 | # fastlane 98 | # 99 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 100 | # screenshots whenever they are needed. 101 | # For more information about the recommended setup visit: 102 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 103 | fastlane/report.xml 104 | fastlane/Preview.html 105 | fastlane/screenshots 106 | fastlane/test_output 107 | *. -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at mohammed.h@simformsolutions.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Way to contribute 2 | 3 | 1. Fork the repo and create your branch from `master`. 4 | 2. Clone the project to your own machine. (Please have a look at [**Readme.md**](https://github.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/blob/master/README.md) to understand how to run this project on your machine) 5 | 3. Commit changes to your own branch 6 | 4. Make sure your code lints. 7 | 5. Push your work back up to your fork. 8 | 6. Issue that pull request! 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Simform Solutions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KMM First Look 2 | 3 | ### What is Kotlin Multiplatform (KMP) 4 | 5 | * Kotlin Multiplatform allows you to share your business logic on multiple platforms like android, iOS and native desktop applications and as a result reducing the amount of time required for development. 6 | 7 | ### Now what is Kotlin Multiplatform Mobile (KMM) ? 8 | 9 | * Kotlin Multiplatform (KMP) and Kotlin Multiplatform Mobile (KMM) are not seperate technologies. You can think of Kotlin Multiplatform Mobile (KMM) as a subset of Kotlin Multiplatform (KMP) which primarily focuses on mobile features. Kotlin Multiplatform Mobile (KMM) uses the multiplatform capabilities of kotlin to share bussiness logic of iOS and Android apps. 10 | 11 | ## Why Kotlin Multiplatform Mobile (KMM) ? 12 | 13 | * Using KMM we can write our business logic once and reuse that for both the platforms android and iOS. We only need to write our platform specific code wherever it's necessary for example to implement our native UI. Does that mean we can have a single application which can run on both Android and iOS app using KMM ? No KMM is specifically design to share only business logic and use native UI to have exact look and feel as simple Android or iOS app. So we still need to create seperate applications for both the platforms. 14 | 15 | ### About this project. 16 | This is a sample KMM project which tries to shares as much as code possible between Android and iOS. 17 | 18 | Android App | iOS App 19 | :-------------------------:|:-------------------------: 20 | | 21 | 22 | ## Pre-requisites 23 | * Android Studio – version 4.2 or higher. 24 | * Xcode – version 11.3 or higher. 25 | * This app displays a list of movies using API from TMDB so you must have a valid api key from TMDB. (If you don't have one you can get it here :- https://www.themoviedb.org/documentation/api) 26 | 27 | ### How to Run Android App :- 28 | * Open Android Studio 29 | * Add KMM Plugin from `Android Studio` -> `Preferences` -> `Plugins` -> `MarketPlace` -> `Search for Kotlin Multiplatform Mobile` -> `Install and Restart Android Studio` 30 | 31 | 32 | * Run app. 33 | 34 | ### How to Run iOS App :- 35 | 36 | * Pretty Simple KMM allows you to run and debug your iOS App from Android Studio itself. Just switch from `androidApp` to `iosApp` in your run configurations menu. (Remember :- This app contains `Pods` so we need to install that from Terminal first before running iOS app from Android Studio or xCode) 37 | * If you are an Android Developer who wants to explore iOS App or a newbie in iOS development and got confused about how to work with pods, we got you covered! 38 | You can find everything about pods here : :- https://cocoapods.org/ 39 | * 40 | * Does that mean you can simply run iOS App without xCode or macOS machine ? No You still need a macOS , xCode (and Simulators) and command line tools installed on your machine to run your iOS App. 41 | * Also you can run this project with xCode. 42 | * Open `iosApp` folder from the project install `pods` with terminal open `.xcworkspace` and run your application. 43 | 44 | ## Find this example useful? :heart: 45 | Support it by joining __[stargazers](https://github.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/stargazers)__ for this repository. :star: 46 | 47 | ## 🤝 How to Contribute 48 | 49 | Whether you're helping us fix bugs, improve the docs, or a feature request, we'd love to have you! :muscle: 50 | 51 | Check out our [**Contributing Guide**](https://github.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/blob/master/CONTRIBUTING.md) for ideas on contributing. 52 | 53 | ## Bugs and Feedback 54 | 55 | For bugs, feature requests, and discussion please use [GitHub Issues](https://github.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/issues). 56 | 57 | ### LICENSE 58 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 59 | -------------------------------------------------------------------------------- /androidApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(Plugins.ANDROID_APPLICATION) 3 | kotlin(Plugins.ANDROID) 4 | id(Plugins.KOTLIN_KAPT) 5 | } 6 | 7 | dependencies { 8 | implementation(defaultFileTree()) 9 | 10 | // Shared module 11 | implementation(project(Libs.shared)) 12 | 13 | //Material Design 14 | implementation(Libs.MATERIAL) 15 | 16 | //SDP SSP 17 | implementation(Libs.SDP) 18 | implementation(Libs.SSP) 19 | 20 | // UI 21 | implementation(Libs.APPCOMPAT) 22 | implementation(Libs.CONSTRAINT_LAYOUT) 23 | implementation(Libs.RECYCLERVIEW) 24 | 25 | // View model 26 | implementation(Libs.VIEW_MODEL) 27 | implementation(Libs.VIEW_MODEL_KTX) 28 | 29 | // Coroutines 30 | implementation(Libs.COROUTINES_CORE) 31 | implementation(Libs.COROUTINES_ANDROID) 32 | 33 | // Glide 34 | implementation(Libs.GLIDE) 35 | } 36 | 37 | android { 38 | compileSdkVersion(Version.COMPILE_SDK) 39 | defaultConfig { 40 | applicationId = App.ID 41 | minSdkVersion(Version.MIN_SDK) 42 | targetSdkVersion(Version.TARGET_SDK) 43 | versionCode = App.Version.CODE 44 | versionName = App.Version.NAME 45 | } 46 | buildTypes { 47 | getByName("release") { 48 | isMinifyEnabled = false 49 | } 50 | } 51 | 52 | compileOptions { 53 | sourceCompatibility = JavaVersion.VERSION_1_8 54 | targetCompatibility = JavaVersion.VERSION_1_8 55 | } 56 | 57 | buildFeatures { 58 | dataBinding = true 59 | } 60 | } -------------------------------------------------------------------------------- /androidApp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /androidApp/src/main/java/com/simform/kmmsample/androidApp/base/Resource.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.androidApp.base 2 | 3 | import com.simform.kmmsample.shared.datamodels.base.CustomException 4 | 5 | /** 6 | * This class is used to pass api status 7 | */ 8 | class Resource(val status: Status, val responseData: T? = null, val throwable: CustomException? = null) { 9 | companion object { 10 | /** 11 | * This fun is used to return success data 12 | */ 13 | fun success(data: T?): Resource { 14 | return Resource(Status.SUCCESS, data) 15 | } 16 | 17 | /** 18 | * This fun is used to return error data 19 | */ 20 | fun error(exception: CustomException): Resource { 21 | return Resource(Status.ERROR, throwable = exception) 22 | } 23 | 24 | /** 25 | * This fun is used to return loading 26 | */ 27 | fun loading(): Resource { 28 | return Resource(Status.LOADING) 29 | } 30 | } 31 | 32 | override fun toString(): String { 33 | return "Resource(status=$status, data=$responseData, throwable=$throwable)" 34 | } 35 | } 36 | 37 | /** 38 | * This is enum class returns the status of apis 39 | */ 40 | enum class Status { 41 | SUCCESS, 42 | ERROR, 43 | LOADING 44 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/com/simform/kmmsample/androidApp/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.androidApp.ui 2 | 3 | import android.app.AlertDialog 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.databinding.DataBindingUtil 7 | import com.simform.kmmsample.androidApp.R 8 | import com.simform.kmmsample.androidApp.base.Status 9 | import com.simform.kmmsample.androidApp.databinding.ActivityMainBinding 10 | import com.simform.kmmsample.shared.datamodels.responsemodels.PopularMoviesResponse 11 | 12 | class MainActivity : AppCompatActivity() { 13 | 14 | private var viewModel = MainViewModel() 15 | private lateinit var binding: ActivityMainBinding 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContentView(R.layout.activity_main) 20 | // SetUp View Binding 21 | setUpBinding() 22 | // Call Movies Api 23 | callApi() 24 | 25 | // Handle Api Response 26 | viewModel.popularMovieResponse.observe(this, { response -> 27 | when (response.status) { 28 | Status.LOADING -> { 29 | viewModel.progressBarVisibility.value = true 30 | } 31 | Status.SUCCESS -> { 32 | viewModel.progressBarVisibility.postValue(false) 33 | response.responseData?.let { movies -> setUpRecyclerView(movies) } 34 | } 35 | Status.ERROR -> { 36 | viewModel.progressBarVisibility.postValue(false) 37 | showAlert(response.throwable?.errorResponse?.statusMessage ?: "") 38 | } 39 | } 40 | }) 41 | } 42 | 43 | private fun showAlert(message: String) { 44 | AlertDialog.Builder(this).setTitle(getString(R.string.alertTitle)).setMessage(message) 45 | .setPositiveButton(android.R.string.ok, null).show() 46 | } 47 | 48 | // SetUp Movie List Adapter 49 | private fun setUpRecyclerView(popularMoviesResponse: PopularMoviesResponse) { 50 | val adapter = MovieListAdapter() 51 | adapter.setList(popularMoviesResponse.results) 52 | binding.recyclerViewMovies.adapter = adapter 53 | } 54 | 55 | private fun setUpBinding() { 56 | binding = DataBindingUtil.setContentView(this, R.layout.activity_main) 57 | binding.apply { 58 | lifecycleOwner = this@MainActivity 59 | viewModel = this@MainActivity.viewModel 60 | } 61 | } 62 | 63 | // Call Get Movies Api 64 | private fun callApi() { 65 | viewModel.callMovieList() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /androidApp/src/main/java/com/simform/kmmsample/androidApp/ui/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.androidApp.ui 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.simform.kmmsample.androidApp.base.Resource 7 | import com.simform.kmmsample.androidApp.utils.initWith 8 | import com.simform.kmmsample.shared.datamodels.responsemodels.PopularMoviesResponse 9 | import com.simform.kmmsample.shared.remote.BaseApiClass 10 | import kotlinx.coroutines.launch 11 | 12 | class MainViewModel : ViewModel() { 13 | 14 | var apiService = BaseApiClass() 15 | var popularMovieResponse = MutableLiveData>() 16 | var progressBarVisibility = MutableLiveData().initWith(true) 17 | 18 | fun callMovieList() { 19 | viewModelScope.launch { 20 | popularMovieResponse.postValue(Resource.loading()) 21 | apiService.getMovies()?.fold( 22 | failed = { 23 | popularMovieResponse.postValue(Resource.error(it)) 24 | }, 25 | succeeded = { 26 | popularMovieResponse.postValue(Resource.success(it)) 27 | } 28 | ) 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/com/simform/kmmsample/androidApp/ui/MovieListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.androidApp.ui 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.simform.kmmsample.androidApp.R 8 | import com.simform.kmmsample.androidApp.databinding.MovieListBinding 9 | import com.simform.kmmsample.shared.datamodels.responsemodels.MovieEntity 10 | 11 | class MovieListAdapter : RecyclerView.Adapter() { 12 | 13 | private var moviesList = arrayListOf() 14 | 15 | fun setList(movies: ArrayList) { 16 | moviesList = movies 17 | notifyDataSetChanged() 18 | } 19 | 20 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 21 | val mBinding: MovieListBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), R.layout.movie_list, parent, false) 22 | return ViewHolder(mBinding) 23 | } 24 | 25 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 26 | holder.bind(moviesList[position]) 27 | } 28 | 29 | override fun getItemCount(): Int = moviesList.size 30 | 31 | inner class ViewHolder(private val binding: MovieListBinding) : 32 | RecyclerView.ViewHolder(binding.root) { 33 | 34 | fun bind(movieEntity: MovieEntity) { 35 | binding.movieEntity = movieEntity 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/com/simform/kmmsample/androidApp/utils/ImageUtils.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.androidApp.utils 2 | 3 | import android.widget.ImageView 4 | import com.bumptech.glide.Glide 5 | import com.bumptech.glide.load.engine.DiskCacheStrategy 6 | import com.bumptech.glide.request.RequestOptions 7 | 8 | fun ImageView.loadUserImage(url: Any?) { 9 | url?.let { image -> 10 | val options = RequestOptions().skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) 11 | val requestBuilder = Glide.with(context).load(image).apply(options) 12 | requestBuilder.into(this) 13 | } 14 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/com/simform/kmmsample/androidApp/utils/MutableLiveDataExtension.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.androidApp.utils 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | 5 | fun MutableLiveData.initWith(data: T): MutableLiveData = this.apply { 6 | value = data 7 | } -------------------------------------------------------------------------------- /androidApp/src/main/java/com/simform/kmmsample/androidApp/utils/binding.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.androidApp.utils 2 | 3 | import android.graphics.drawable.Drawable 4 | import android.widget.ImageView 5 | import androidx.databinding.BindingAdapter 6 | import com.bumptech.glide.Glide 7 | 8 | @BindingAdapter(value = ["imageUrl", "placeHolder"], requireAll = false) 9 | fun bindImage(view: ImageView, url: String?, placeHolder: Drawable?) { 10 | Glide.with(view.context) 11 | .load(url) 12 | .placeholder(placeHolder) 13 | .into(view) 14 | } 15 | -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/androidApp/src/main/res/drawable/placeholder.jpg -------------------------------------------------------------------------------- /androidApp/src/main/res/drawable/tmdb.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /androidApp/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 18 | 19 | 27 | 28 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /androidApp/src/main/res/layout/movie_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 21 | 22 | 25 | 26 | 38 | 39 | 54 | 55 | 66 | 67 | 79 | 80 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /androidApp/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2D647D 4 | #2D647D 5 | #46a0c6 6 | #1B3C4B 7 | -------------------------------------------------------------------------------- /androidApp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | KMM Sample 4 | %s / 10 5 | TMDB 6 | Alert 7 | -------------------------------------------------------------------------------- /androidApp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | gradlePluginPortal() 4 | jcenter() 5 | google() 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Version.KOTLIN_VERSION}") 10 | classpath("com.android.tools.build:gradle:${Version.ANDROID_GRADLE}") 11 | classpath("org.jetbrains.kotlin:kotlin-serialization:${Version.KOTLIN_VERSION}") 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | mavenCentral() 20 | jitpack() 21 | } 22 | } -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | repositories { 2 | jcenter() 3 | } 4 | 5 | plugins { 6 | `kotlin-dsl` 7 | } 8 | -------------------------------------------------------------------------------- /buildSrc/src/main/java/App.kt: -------------------------------------------------------------------------------- 1 | object App { 2 | const val ID = "com.example.kmmsample.androidApp" 3 | 4 | object Version { 5 | const val CODE = 1 6 | const val NAME = "1.1" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /buildSrc/src/main/java/Extensions.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Project 2 | import org.gradle.api.artifacts.dsl.RepositoryHandler 3 | import java.net.URI 4 | 5 | /** 6 | * Extension to add file tree dependency 7 | */ 8 | fun Project.defaultFileTree() = fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))) 9 | 10 | /** 11 | * Adds preference maven repository. 12 | */ 13 | fun RepositoryHandler.jitpack() = maven { url = URI(Urls.JITPACK) } 14 | -------------------------------------------------------------------------------- /buildSrc/src/main/java/Libs.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all libraries. 3 | */ 4 | object Libs { 5 | 6 | //Shared 7 | const val shared = ":shared" 8 | 9 | // Kotlin 10 | const val KOTLIN_STDLIB = "org.jetbrains.kotlin:kotlin-stdlib:${Version.KOTLIN_VERSION}" 11 | 12 | // UI 13 | const val CONSTRAINT_LAYOUT = "androidx.constraintlayout:constraintlayout:${Version.CONSTRAINT_LAYOUT}" 14 | const val RECYCLERVIEW = "androidx.recyclerview:recyclerview:${Version.APPCOMPAT}" 15 | 16 | // ViewModel 17 | const val CORE_KTX = "androidx.core:core-ktx:${Version.CORE_KTX}" 18 | const val VIEW_MODEL = "androidx.lifecycle:lifecycle-viewmodel:${Version.ARCH_VERSION}" 19 | const val VIEW_MODEL_KTX = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Version.ARCH_VERSION}" 20 | const val APPCOMPAT = "androidx.appcompat:appcompat:${Version.APPCOMPAT}" 21 | 22 | // Coroutines 23 | const val COROUTINES_CORE = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Version.COROUTINES}" 24 | const val COROUTINES_ANDROID = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Version.COROUTINES}" 25 | const val COROUTINES_TEST = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Version.COROUTINES}" 26 | 27 | // Glide 28 | const val GLIDE = "com.github.bumptech.glide:glide:${Version.GLIDE}" 29 | 30 | //SDP 31 | const val SDP = "com.intuit.sdp:sdp-android:${Version.SDP}" 32 | 33 | //SSP 34 | const val SSP = "com.intuit.ssp:ssp-android:${Version.SSP}" 35 | 36 | //MATERIAL 37 | const val MATERIAL = "com.google.android.material:material:${Version.MATERIAL}" 38 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/Plugins.kt: -------------------------------------------------------------------------------- 1 | object Plugins { 2 | const val ANDROID_APPLICATION = "com.android.application" 3 | const val ANDROID = "android" 4 | const val KOTLIN_KAPT = "kotlin-kapt" 5 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/SharedLibs.kt: -------------------------------------------------------------------------------- 1 | object SharedLibs { 2 | //Common Libraries 3 | 4 | //Coroutines Core 5 | const val COROUINES_CORE = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${SharedVersions.COROUTINES_VERSION}" 6 | 7 | //Ktor features 8 | const val KTOR = "io.ktor:ktor-client-core:${SharedVersions.KTOR_VERSION}" 9 | const val KTOR_SERIALIZER = "io.ktor:ktor-client-serialization:${SharedVersions.KTOR_VERSION}" 10 | 11 | // KotlinX Serialization 12 | const val KOTLINX_SERIALIZATION= "org.jetbrains.kotlinx:kotlinx-serialization-json:" + 13 | SharedVersions.KOTLINX_SERIALIZATION_VERSION 14 | 15 | //Android Specific Libraries 16 | 17 | //Ktor and it's features 18 | const val KTOR_ANDROID = "io.ktor:ktor-client-android:${SharedVersions.KTOR_VERSION}" 19 | const val KTOR_JSON_ANDROID = "io.ktor:ktor-client-json:${SharedVersions.KTOR_VERSION}" 20 | const val KTOR_SERIALIZER_ANDROID = "io.ktor:ktor-client-serialization-jvm:${SharedVersions.KTOR_VERSION}" 21 | 22 | //Kotlinx Serializer 23 | const val KOTLINX_SERIALIZER_ANDROID = "org.jetbrains.kotlinx:kotlinx-serialization-json:" + 24 | SharedVersions.KOTLINX_SERIALIZATION_VERSION 25 | 26 | //iOS Specific Libraries 27 | 28 | //Ktor for iOS 29 | const val KTOR_IOS = "io.ktor:ktor-client-ios:${SharedVersions.KTOR_VERSION}" 30 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/SharedPlugins.kt: -------------------------------------------------------------------------------- 1 | //Shared native.Plugins 2 | object SharedPlugins { 3 | const val SHARED_MODULE = "com.android.library" 4 | const val MULTIPLATFORM = "multiplatform" 5 | const val SERIALIZATION = "plugin.serialization" 6 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/SharedVersions.kt: -------------------------------------------------------------------------------- 1 | //Shared Versions 2 | object SharedVersions { 3 | const val COROUTINES_VERSION = "1.4.2-native-mt" 4 | const val KTOR_VERSION = "1.6.2" 5 | const val KOTLINX_SERIALIZATION_VERSION = "1.2.2" 6 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/Urls.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Urls for gradle. 3 | */ 4 | object Urls { 5 | const val JITPACK = "https://jitpack.io" 6 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/Versions.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains versions for [Libs]. 3 | */ 4 | object Version { 5 | const val COMPILE_SDK = 31 6 | const val MIN_SDK = 21 7 | const val TARGET_SDK = 31 8 | 9 | const val APPCOMPAT = "1.2.0" 10 | const val ANDROID_GRADLE = "7.0.1" 11 | const val CORE_KTX = "1.6.0" 12 | const val ARCH_VERSION = "2.3.1" 13 | const val CONSTRAINT_LAYOUT = "2.1.0" 14 | const val COROUTINES = "1.5.1" 15 | const val GLIDE = "4.12.0" 16 | const val KOTLIN_VERSION = "1.5.21" 17 | const val MATERIAL = "1.2.1" 18 | const val SDP = "1.0.6" 19 | const val SSP = "1.0.6" 20 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | xcodeproj=./iosApp 3 | android.useAndroidX=true 4 | kotlin.mpp.enableGranularSourceSetsMetadata=true 5 | kotlin.native.enableDependencyPropagation=false 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jan 22 12:02:03 IST 2021 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-7.0.2-bin.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /iosApp/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'iosApp' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for iosApp 9 | 10 | pod 'Kingfisher', '5.15.8' # Used for download images 11 | 12 | end 13 | 14 | -------------------------------------------------------------------------------- /iosApp/iosApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1FE6A11B221D69282A6FC03B /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 583BAA25BEE980389E672006 /* Pods_iosApp.framework */; }; 11 | 7555FF7F242A565900829871 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF7E242A565900829871 /* AppDelegate.swift */; }; 12 | 7555FF85242A565B00829871 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7555FF84242A565B00829871 /* Assets.xcassets */; }; 13 | 7555FF8B242A565B00829871 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7555FF89242A565B00829871 /* LaunchScreen.storyboard */; }; 14 | 7555FF96242A565B00829871 /* iosAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF95242A565B00829871 /* iosAppTests.swift */; }; 15 | 7555FFA1242A565B00829871 /* iosAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FFA0242A565B00829871 /* iosAppUITests.swift */; }; 16 | 7555FFB2242A642300829871 /* shared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7555FFB1242A642300829871 /* shared.framework */; }; 17 | 7555FFB3242A642300829871 /* shared.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7555FFB1242A642300829871 /* shared.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | E305F24C25E14589008BA605 /* MoviesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E305F24B25E14589008BA605 /* MoviesCell.swift */; }; 19 | E325AA9D25F20BE300266C70 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E325AA9C25F20BE300266C70 /* Colors.xcassets */; }; 20 | E34F567F25EFECF8003AFD2C /* Structures.swift in Sources */ = {isa = PBXBuildFile; fileRef = E34F567E25EFECF8003AFD2C /* Structures.swift */; }; 21 | E3603A2125F1FD40000F381C /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3603A2025F1FD40000F381C /* MainViewModel.swift */; }; 22 | EB7E684B25BAB4B000DFCBB6 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7E684A25BAB4B000DFCBB6 /* MainViewController.swift */; }; 23 | EB7E685025BAB4CE00DFCBB6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EB7E684F25BAB4CE00DFCBB6 /* Main.storyboard */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | 7555FF92242A565B00829871 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 7555FF73242A565900829871 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 7555FF7A242A565900829871; 32 | remoteInfo = iosApp; 33 | }; 34 | 7555FF9D242A565B00829871 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 7555FF73242A565900829871 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 7555FF7A242A565900829871; 39 | remoteInfo = iosApp; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXCopyFilesBuildPhase section */ 44 | 7555FFB4242A642300829871 /* Embed Frameworks */ = { 45 | isa = PBXCopyFilesBuildPhase; 46 | buildActionMask = 2147483647; 47 | dstPath = ""; 48 | dstSubfolderSpec = 10; 49 | files = ( 50 | 7555FFB3242A642300829871 /* shared.framework in Embed Frameworks */, 51 | ); 52 | name = "Embed Frameworks"; 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXCopyFilesBuildPhase section */ 56 | 57 | /* Begin PBXFileReference section */ 58 | 0182BEA8F1606602BF5E3669 /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; }; 59 | 583BAA25BEE980389E672006 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | 7555FF7E242A565900829871 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 62 | 7555FF84242A565B00829871 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 63 | 7555FF8A242A565B00829871 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 64 | 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 65 | 7555FF91242A565B00829871 /* iosAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iosAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | 7555FF95242A565B00829871 /* iosAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosAppTests.swift; sourceTree = ""; }; 67 | 7555FF97242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 68 | 7555FF9C242A565B00829871 /* iosAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iosAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | 7555FFA0242A565B00829871 /* iosAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosAppUITests.swift; sourceTree = ""; }; 70 | 7555FFA2242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 71 | 7555FFB1242A642300829871 /* shared.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = shared.framework; path = "../shared/build/xcode-frameworks/shared.framework"; sourceTree = ""; }; 72 | E305F24B25E14589008BA605 /* MoviesCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoviesCell.swift; sourceTree = ""; }; 73 | E305F25925E14DEB008BA605 /* Kingfisher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74 | E325AA9C25F20BE300266C70 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; 75 | E34F567E25EFECF8003AFD2C /* Structures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Structures.swift; sourceTree = ""; }; 76 | E3603A2025F1FD40000F381C /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; }; 77 | E8E26546DCD6A90A53C35C5D /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; 78 | EB7E684A25BAB4B000DFCBB6 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 79 | EB7E684F25BAB4CE00DFCBB6 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 80 | /* End PBXFileReference section */ 81 | 82 | /* Begin PBXFrameworksBuildPhase section */ 83 | 7555FF78242A565900829871 /* Frameworks */ = { 84 | isa = PBXFrameworksBuildPhase; 85 | buildActionMask = 2147483647; 86 | files = ( 87 | 7555FFB2242A642300829871 /* shared.framework in Frameworks */, 88 | 1FE6A11B221D69282A6FC03B /* Pods_iosApp.framework in Frameworks */, 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | 7555FF8E242A565B00829871 /* Frameworks */ = { 93 | isa = PBXFrameworksBuildPhase; 94 | buildActionMask = 2147483647; 95 | files = ( 96 | ); 97 | runOnlyForDeploymentPostprocessing = 0; 98 | }; 99 | 7555FF99242A565B00829871 /* Frameworks */ = { 100 | isa = PBXFrameworksBuildPhase; 101 | buildActionMask = 2147483647; 102 | files = ( 103 | ); 104 | runOnlyForDeploymentPostprocessing = 0; 105 | }; 106 | /* End PBXFrameworksBuildPhase section */ 107 | 108 | /* Begin PBXGroup section */ 109 | 7555FF72242A565900829871 = { 110 | isa = PBXGroup; 111 | children = ( 112 | 7555FF7D242A565900829871 /* iosApp */, 113 | 7555FF94242A565B00829871 /* iosAppTests */, 114 | 7555FF9F242A565B00829871 /* iosAppUITests */, 115 | 7555FF7C242A565900829871 /* Products */, 116 | 7555FFB0242A642200829871 /* Frameworks */, 117 | 9C56648237A1BFDD39D50F35 /* Pods */, 118 | ); 119 | sourceTree = ""; 120 | }; 121 | 7555FF7C242A565900829871 /* Products */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 7555FF7B242A565900829871 /* iosApp.app */, 125 | 7555FF91242A565B00829871 /* iosAppTests.xctest */, 126 | 7555FF9C242A565B00829871 /* iosAppUITests.xctest */, 127 | ); 128 | name = Products; 129 | sourceTree = ""; 130 | }; 131 | 7555FF7D242A565900829871 /* iosApp */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | E34F567D25EFECE5003AFD2C /* Constants */, 135 | EB7E684E25BAB4C100DFCBB6 /* Storyboards */, 136 | EB7E684525BAB48500DFCBB6 /* ViewControllers */, 137 | 7555FF7E242A565900829871 /* AppDelegate.swift */, 138 | 7555FF84242A565B00829871 /* Assets.xcassets */, 139 | 7555FF8C242A565B00829871 /* Info.plist */, 140 | E325AA9C25F20BE300266C70 /* Colors.xcassets */, 141 | ); 142 | path = iosApp; 143 | sourceTree = ""; 144 | }; 145 | 7555FF94242A565B00829871 /* iosAppTests */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 7555FF95242A565B00829871 /* iosAppTests.swift */, 149 | 7555FF97242A565B00829871 /* Info.plist */, 150 | ); 151 | path = iosAppTests; 152 | sourceTree = ""; 153 | }; 154 | 7555FF9F242A565B00829871 /* iosAppUITests */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 7555FFA0242A565B00829871 /* iosAppUITests.swift */, 158 | 7555FFA2242A565B00829871 /* Info.plist */, 159 | ); 160 | path = iosAppUITests; 161 | sourceTree = ""; 162 | }; 163 | 7555FFB0242A642200829871 /* Frameworks */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | E305F25925E14DEB008BA605 /* Kingfisher.framework */, 167 | 7555FFB1242A642300829871 /* shared.framework */, 168 | 583BAA25BEE980389E672006 /* Pods_iosApp.framework */, 169 | ); 170 | name = Frameworks; 171 | sourceTree = ""; 172 | }; 173 | 9C56648237A1BFDD39D50F35 /* Pods */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | E8E26546DCD6A90A53C35C5D /* Pods-iosApp.debug.xcconfig */, 177 | 0182BEA8F1606602BF5E3669 /* Pods-iosApp.release.xcconfig */, 178 | ); 179 | path = Pods; 180 | sourceTree = ""; 181 | }; 182 | E34F567D25EFECE5003AFD2C /* Constants */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | E34F567E25EFECF8003AFD2C /* Structures.swift */, 186 | ); 187 | path = Constants; 188 | sourceTree = ""; 189 | }; 190 | EB7E684525BAB48500DFCBB6 /* ViewControllers */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | EB7E684A25BAB4B000DFCBB6 /* MainViewController.swift */, 194 | E305F24B25E14589008BA605 /* MoviesCell.swift */, 195 | E3603A2025F1FD40000F381C /* MainViewModel.swift */, 196 | ); 197 | path = ViewControllers; 198 | sourceTree = ""; 199 | }; 200 | EB7E684E25BAB4C100DFCBB6 /* Storyboards */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | 7555FF89242A565B00829871 /* LaunchScreen.storyboard */, 204 | EB7E684F25BAB4CE00DFCBB6 /* Main.storyboard */, 205 | ); 206 | path = Storyboards; 207 | sourceTree = ""; 208 | }; 209 | /* End PBXGroup section */ 210 | 211 | /* Begin PBXNativeTarget section */ 212 | 7555FF7A242A565900829871 /* iosApp */ = { 213 | isa = PBXNativeTarget; 214 | buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; 215 | buildPhases = ( 216 | F64F09897066CC5A18C320C1 /* [CP] Check Pods Manifest.lock */, 217 | 7555FFB5242A651A00829871 /* ShellScript */, 218 | 7555FF77242A565900829871 /* Sources */, 219 | 7555FF78242A565900829871 /* Frameworks */, 220 | 7555FF79242A565900829871 /* Resources */, 221 | 7555FFB4242A642300829871 /* Embed Frameworks */, 222 | 3794DCBBB2254BB3B1B04787 /* [CP] Embed Pods Frameworks */, 223 | ); 224 | buildRules = ( 225 | ); 226 | dependencies = ( 227 | ); 228 | name = iosApp; 229 | productName = iosApp; 230 | productReference = 7555FF7B242A565900829871 /* iosApp.app */; 231 | productType = "com.apple.product-type.application"; 232 | }; 233 | 7555FF90242A565B00829871 /* iosAppTests */ = { 234 | isa = PBXNativeTarget; 235 | buildConfigurationList = 7555FFA8242A565B00829871 /* Build configuration list for PBXNativeTarget "iosAppTests" */; 236 | buildPhases = ( 237 | 7555FF8D242A565B00829871 /* Sources */, 238 | 7555FF8E242A565B00829871 /* Frameworks */, 239 | 7555FF8F242A565B00829871 /* Resources */, 240 | ); 241 | buildRules = ( 242 | ); 243 | dependencies = ( 244 | 7555FF93242A565B00829871 /* PBXTargetDependency */, 245 | ); 246 | name = iosAppTests; 247 | productName = iosAppTests; 248 | productReference = 7555FF91242A565B00829871 /* iosAppTests.xctest */; 249 | productType = "com.apple.product-type.bundle.unit-test"; 250 | }; 251 | 7555FF9B242A565B00829871 /* iosAppUITests */ = { 252 | isa = PBXNativeTarget; 253 | buildConfigurationList = 7555FFAB242A565B00829871 /* Build configuration list for PBXNativeTarget "iosAppUITests" */; 254 | buildPhases = ( 255 | 7555FF98242A565B00829871 /* Sources */, 256 | 7555FF99242A565B00829871 /* Frameworks */, 257 | 7555FF9A242A565B00829871 /* Resources */, 258 | ); 259 | buildRules = ( 260 | ); 261 | dependencies = ( 262 | 7555FF9E242A565B00829871 /* PBXTargetDependency */, 263 | ); 264 | name = iosAppUITests; 265 | productName = iosAppUITests; 266 | productReference = 7555FF9C242A565B00829871 /* iosAppUITests.xctest */; 267 | productType = "com.apple.product-type.bundle.ui-testing"; 268 | }; 269 | /* End PBXNativeTarget section */ 270 | 271 | /* Begin PBXProject section */ 272 | 7555FF73242A565900829871 /* Project object */ = { 273 | isa = PBXProject; 274 | attributes = { 275 | LastSwiftUpdateCheck = 1130; 276 | LastUpgradeCheck = 1130; 277 | ORGANIZATIONNAME = orgName; 278 | TargetAttributes = { 279 | 7555FF7A242A565900829871 = { 280 | CreatedOnToolsVersion = 11.3.1; 281 | }; 282 | 7555FF90242A565B00829871 = { 283 | CreatedOnToolsVersion = 11.3.1; 284 | TestTargetID = 7555FF7A242A565900829871; 285 | }; 286 | 7555FF9B242A565B00829871 = { 287 | CreatedOnToolsVersion = 11.3.1; 288 | TestTargetID = 7555FF7A242A565900829871; 289 | }; 290 | }; 291 | }; 292 | buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; 293 | compatibilityVersion = "Xcode 9.3"; 294 | developmentRegion = en; 295 | hasScannedForEncodings = 0; 296 | knownRegions = ( 297 | en, 298 | Base, 299 | ); 300 | mainGroup = 7555FF72242A565900829871; 301 | productRefGroup = 7555FF7C242A565900829871 /* Products */; 302 | projectDirPath = ""; 303 | projectRoot = ""; 304 | targets = ( 305 | 7555FF7A242A565900829871 /* iosApp */, 306 | 7555FF90242A565B00829871 /* iosAppTests */, 307 | 7555FF9B242A565B00829871 /* iosAppUITests */, 308 | ); 309 | }; 310 | /* End PBXProject section */ 311 | 312 | /* Begin PBXResourcesBuildPhase section */ 313 | 7555FF79242A565900829871 /* Resources */ = { 314 | isa = PBXResourcesBuildPhase; 315 | buildActionMask = 2147483647; 316 | files = ( 317 | 7555FF8B242A565B00829871 /* LaunchScreen.storyboard in Resources */, 318 | E325AA9D25F20BE300266C70 /* Colors.xcassets in Resources */, 319 | 7555FF85242A565B00829871 /* Assets.xcassets in Resources */, 320 | EB7E685025BAB4CE00DFCBB6 /* Main.storyboard in Resources */, 321 | ); 322 | runOnlyForDeploymentPostprocessing = 0; 323 | }; 324 | 7555FF8F242A565B00829871 /* Resources */ = { 325 | isa = PBXResourcesBuildPhase; 326 | buildActionMask = 2147483647; 327 | files = ( 328 | ); 329 | runOnlyForDeploymentPostprocessing = 0; 330 | }; 331 | 7555FF9A242A565B00829871 /* Resources */ = { 332 | isa = PBXResourcesBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | ); 336 | runOnlyForDeploymentPostprocessing = 0; 337 | }; 338 | /* End PBXResourcesBuildPhase section */ 339 | 340 | /* Begin PBXShellScriptBuildPhase section */ 341 | 3794DCBBB2254BB3B1B04787 /* [CP] Embed Pods Frameworks */ = { 342 | isa = PBXShellScriptBuildPhase; 343 | buildActionMask = 2147483647; 344 | files = ( 345 | ); 346 | inputFileListPaths = ( 347 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", 348 | ); 349 | name = "[CP] Embed Pods Frameworks"; 350 | outputFileListPaths = ( 351 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", 352 | ); 353 | runOnlyForDeploymentPostprocessing = 0; 354 | shellPath = /bin/sh; 355 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh\"\n"; 356 | showEnvVarsInLog = 0; 357 | }; 358 | 7555FFB5242A651A00829871 /* ShellScript */ = { 359 | isa = PBXShellScriptBuildPhase; 360 | buildActionMask = 2147483647; 361 | files = ( 362 | ); 363 | inputFileListPaths = ( 364 | ); 365 | inputPaths = ( 366 | ); 367 | outputFileListPaths = ( 368 | ); 369 | outputPaths = ( 370 | ); 371 | runOnlyForDeploymentPostprocessing = 0; 372 | shellPath = /bin/sh; 373 | shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}\n"; 374 | }; 375 | F64F09897066CC5A18C320C1 /* [CP] Check Pods Manifest.lock */ = { 376 | isa = PBXShellScriptBuildPhase; 377 | buildActionMask = 2147483647; 378 | files = ( 379 | ); 380 | inputFileListPaths = ( 381 | ); 382 | inputPaths = ( 383 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 384 | "${PODS_ROOT}/Manifest.lock", 385 | ); 386 | name = "[CP] Check Pods Manifest.lock"; 387 | outputFileListPaths = ( 388 | ); 389 | outputPaths = ( 390 | "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt", 391 | ); 392 | runOnlyForDeploymentPostprocessing = 0; 393 | shellPath = /bin/sh; 394 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 395 | showEnvVarsInLog = 0; 396 | }; 397 | /* End PBXShellScriptBuildPhase section */ 398 | 399 | /* Begin PBXSourcesBuildPhase section */ 400 | 7555FF77242A565900829871 /* Sources */ = { 401 | isa = PBXSourcesBuildPhase; 402 | buildActionMask = 2147483647; 403 | files = ( 404 | E3603A2125F1FD40000F381C /* MainViewModel.swift in Sources */, 405 | 7555FF7F242A565900829871 /* AppDelegate.swift in Sources */, 406 | E34F567F25EFECF8003AFD2C /* Structures.swift in Sources */, 407 | E305F24C25E14589008BA605 /* MoviesCell.swift in Sources */, 408 | EB7E684B25BAB4B000DFCBB6 /* MainViewController.swift in Sources */, 409 | ); 410 | runOnlyForDeploymentPostprocessing = 0; 411 | }; 412 | 7555FF8D242A565B00829871 /* Sources */ = { 413 | isa = PBXSourcesBuildPhase; 414 | buildActionMask = 2147483647; 415 | files = ( 416 | 7555FF96242A565B00829871 /* iosAppTests.swift in Sources */, 417 | ); 418 | runOnlyForDeploymentPostprocessing = 0; 419 | }; 420 | 7555FF98242A565B00829871 /* Sources */ = { 421 | isa = PBXSourcesBuildPhase; 422 | buildActionMask = 2147483647; 423 | files = ( 424 | 7555FFA1242A565B00829871 /* iosAppUITests.swift in Sources */, 425 | ); 426 | runOnlyForDeploymentPostprocessing = 0; 427 | }; 428 | /* End PBXSourcesBuildPhase section */ 429 | 430 | /* Begin PBXTargetDependency section */ 431 | 7555FF93242A565B00829871 /* PBXTargetDependency */ = { 432 | isa = PBXTargetDependency; 433 | target = 7555FF7A242A565900829871 /* iosApp */; 434 | targetProxy = 7555FF92242A565B00829871 /* PBXContainerItemProxy */; 435 | }; 436 | 7555FF9E242A565B00829871 /* PBXTargetDependency */ = { 437 | isa = PBXTargetDependency; 438 | target = 7555FF7A242A565900829871 /* iosApp */; 439 | targetProxy = 7555FF9D242A565B00829871 /* PBXContainerItemProxy */; 440 | }; 441 | /* End PBXTargetDependency section */ 442 | 443 | /* Begin PBXVariantGroup section */ 444 | 7555FF89242A565B00829871 /* LaunchScreen.storyboard */ = { 445 | isa = PBXVariantGroup; 446 | children = ( 447 | 7555FF8A242A565B00829871 /* Base */, 448 | ); 449 | name = LaunchScreen.storyboard; 450 | sourceTree = ""; 451 | }; 452 | /* End PBXVariantGroup section */ 453 | 454 | /* Begin XCBuildConfiguration section */ 455 | 7555FFA3242A565B00829871 /* Debug */ = { 456 | isa = XCBuildConfiguration; 457 | buildSettings = { 458 | ALWAYS_SEARCH_USER_PATHS = NO; 459 | CLANG_ANALYZER_NONNULL = YES; 460 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 461 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 462 | CLANG_CXX_LIBRARY = "libc++"; 463 | CLANG_ENABLE_MODULES = YES; 464 | CLANG_ENABLE_OBJC_ARC = YES; 465 | CLANG_ENABLE_OBJC_WEAK = YES; 466 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 467 | CLANG_WARN_BOOL_CONVERSION = YES; 468 | CLANG_WARN_COMMA = YES; 469 | CLANG_WARN_CONSTANT_CONVERSION = YES; 470 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 471 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 472 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 473 | CLANG_WARN_EMPTY_BODY = YES; 474 | CLANG_WARN_ENUM_CONVERSION = YES; 475 | CLANG_WARN_INFINITE_RECURSION = YES; 476 | CLANG_WARN_INT_CONVERSION = YES; 477 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 478 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 479 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 480 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 481 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 482 | CLANG_WARN_STRICT_PROTOTYPES = YES; 483 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 484 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 485 | CLANG_WARN_UNREACHABLE_CODE = YES; 486 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 487 | COPY_PHASE_STRIP = NO; 488 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 489 | ENABLE_STRICT_OBJC_MSGSEND = YES; 490 | ENABLE_TESTABILITY = YES; 491 | GCC_C_LANGUAGE_STANDARD = gnu11; 492 | GCC_DYNAMIC_NO_PIC = NO; 493 | GCC_NO_COMMON_BLOCKS = YES; 494 | GCC_OPTIMIZATION_LEVEL = 0; 495 | GCC_PREPROCESSOR_DEFINITIONS = ( 496 | "DEBUG=1", 497 | "$(inherited)", 498 | ); 499 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 500 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 501 | GCC_WARN_UNDECLARED_SELECTOR = YES; 502 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 503 | GCC_WARN_UNUSED_FUNCTION = YES; 504 | GCC_WARN_UNUSED_VARIABLE = YES; 505 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 506 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 507 | MTL_FAST_MATH = YES; 508 | ONLY_ACTIVE_ARCH = YES; 509 | SDKROOT = iphoneos; 510 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 511 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 512 | }; 513 | name = Debug; 514 | }; 515 | 7555FFA4242A565B00829871 /* Release */ = { 516 | isa = XCBuildConfiguration; 517 | buildSettings = { 518 | ALWAYS_SEARCH_USER_PATHS = NO; 519 | CLANG_ANALYZER_NONNULL = YES; 520 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 521 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 522 | CLANG_CXX_LIBRARY = "libc++"; 523 | CLANG_ENABLE_MODULES = YES; 524 | CLANG_ENABLE_OBJC_ARC = YES; 525 | CLANG_ENABLE_OBJC_WEAK = YES; 526 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 527 | CLANG_WARN_BOOL_CONVERSION = YES; 528 | CLANG_WARN_COMMA = YES; 529 | CLANG_WARN_CONSTANT_CONVERSION = YES; 530 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 531 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 532 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 533 | CLANG_WARN_EMPTY_BODY = YES; 534 | CLANG_WARN_ENUM_CONVERSION = YES; 535 | CLANG_WARN_INFINITE_RECURSION = YES; 536 | CLANG_WARN_INT_CONVERSION = YES; 537 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 538 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 539 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 540 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 541 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 542 | CLANG_WARN_STRICT_PROTOTYPES = YES; 543 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 544 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 545 | CLANG_WARN_UNREACHABLE_CODE = YES; 546 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 547 | COPY_PHASE_STRIP = NO; 548 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 549 | ENABLE_NS_ASSERTIONS = NO; 550 | ENABLE_STRICT_OBJC_MSGSEND = YES; 551 | GCC_C_LANGUAGE_STANDARD = gnu11; 552 | GCC_NO_COMMON_BLOCKS = YES; 553 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 554 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 555 | GCC_WARN_UNDECLARED_SELECTOR = YES; 556 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 557 | GCC_WARN_UNUSED_FUNCTION = YES; 558 | GCC_WARN_UNUSED_VARIABLE = YES; 559 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 560 | MTL_ENABLE_DEBUG_INFO = NO; 561 | MTL_FAST_MATH = YES; 562 | SDKROOT = iphoneos; 563 | SWIFT_COMPILATION_MODE = wholemodule; 564 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 565 | VALIDATE_PRODUCT = YES; 566 | }; 567 | name = Release; 568 | }; 569 | 7555FFA6242A565B00829871 /* Debug */ = { 570 | isa = XCBuildConfiguration; 571 | baseConfigurationReference = E8E26546DCD6A90A53C35C5D /* Pods-iosApp.debug.xcconfig */; 572 | buildSettings = { 573 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 574 | CODE_SIGN_STYLE = Automatic; 575 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; 576 | ENABLE_PREVIEWS = YES; 577 | FRAMEWORK_SEARCH_PATHS = ( 578 | "$(SRCROOT)/../shared/build/xcode-frameworks", 579 | "$(inherited)", 580 | ); 581 | INFOPLIST_FILE = iosApp/Info.plist; 582 | LD_RUNPATH_SEARCH_PATHS = ( 583 | "$(inherited)", 584 | "@executable_path/Frameworks", 585 | ); 586 | PRODUCT_BUNDLE_IDENTIFIER = com.simform.kmmsample.iosApp; 587 | PRODUCT_NAME = "$(TARGET_NAME)"; 588 | SWIFT_VERSION = 5.0; 589 | TARGETED_DEVICE_FAMILY = "1,2"; 590 | }; 591 | name = Debug; 592 | }; 593 | 7555FFA7242A565B00829871 /* Release */ = { 594 | isa = XCBuildConfiguration; 595 | baseConfigurationReference = 0182BEA8F1606602BF5E3669 /* Pods-iosApp.release.xcconfig */; 596 | buildSettings = { 597 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 598 | CODE_SIGN_STYLE = Automatic; 599 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; 600 | ENABLE_PREVIEWS = YES; 601 | FRAMEWORK_SEARCH_PATHS = ( 602 | "$(SRCROOT)/../shared/build/xcode-frameworks", 603 | "$(inherited)", 604 | ); 605 | INFOPLIST_FILE = iosApp/Info.plist; 606 | LD_RUNPATH_SEARCH_PATHS = ( 607 | "$(inherited)", 608 | "@executable_path/Frameworks", 609 | ); 610 | PRODUCT_BUNDLE_IDENTIFIER = com.simform.kmmsample.iosApp; 611 | PRODUCT_NAME = "$(TARGET_NAME)"; 612 | SWIFT_VERSION = 5.0; 613 | TARGETED_DEVICE_FAMILY = "1,2"; 614 | }; 615 | name = Release; 616 | }; 617 | 7555FFA9242A565B00829871 /* Debug */ = { 618 | isa = XCBuildConfiguration; 619 | buildSettings = { 620 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 621 | BUNDLE_LOADER = "$(TEST_HOST)"; 622 | CODE_SIGN_STYLE = Automatic; 623 | INFOPLIST_FILE = iosAppTests/Info.plist; 624 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 625 | LD_RUNPATH_SEARCH_PATHS = ( 626 | "$(inherited)", 627 | "@executable_path/Frameworks", 628 | "@loader_path/Frameworks", 629 | ); 630 | PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosAppTests; 631 | PRODUCT_NAME = "$(TARGET_NAME)"; 632 | SWIFT_VERSION = 5.0; 633 | TARGETED_DEVICE_FAMILY = "1,2"; 634 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iosApp.app/iosApp"; 635 | }; 636 | name = Debug; 637 | }; 638 | 7555FFAA242A565B00829871 /* Release */ = { 639 | isa = XCBuildConfiguration; 640 | buildSettings = { 641 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 642 | BUNDLE_LOADER = "$(TEST_HOST)"; 643 | CODE_SIGN_STYLE = Automatic; 644 | INFOPLIST_FILE = iosAppTests/Info.plist; 645 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 646 | LD_RUNPATH_SEARCH_PATHS = ( 647 | "$(inherited)", 648 | "@executable_path/Frameworks", 649 | "@loader_path/Frameworks", 650 | ); 651 | PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosAppTests; 652 | PRODUCT_NAME = "$(TARGET_NAME)"; 653 | SWIFT_VERSION = 5.0; 654 | TARGETED_DEVICE_FAMILY = "1,2"; 655 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iosApp.app/iosApp"; 656 | }; 657 | name = Release; 658 | }; 659 | 7555FFAC242A565B00829871 /* Debug */ = { 660 | isa = XCBuildConfiguration; 661 | buildSettings = { 662 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 663 | CODE_SIGN_STYLE = Automatic; 664 | INFOPLIST_FILE = iosAppUITests/Info.plist; 665 | LD_RUNPATH_SEARCH_PATHS = ( 666 | "$(inherited)", 667 | "@executable_path/Frameworks", 668 | "@loader_path/Frameworks", 669 | ); 670 | PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosAppUITests; 671 | PRODUCT_NAME = "$(TARGET_NAME)"; 672 | SWIFT_VERSION = 5.0; 673 | TARGETED_DEVICE_FAMILY = "1,2"; 674 | TEST_TARGET_NAME = iosApp; 675 | }; 676 | name = Debug; 677 | }; 678 | 7555FFAD242A565B00829871 /* Release */ = { 679 | isa = XCBuildConfiguration; 680 | buildSettings = { 681 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 682 | CODE_SIGN_STYLE = Automatic; 683 | INFOPLIST_FILE = iosAppUITests/Info.plist; 684 | LD_RUNPATH_SEARCH_PATHS = ( 685 | "$(inherited)", 686 | "@executable_path/Frameworks", 687 | "@loader_path/Frameworks", 688 | ); 689 | PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosAppUITests; 690 | PRODUCT_NAME = "$(TARGET_NAME)"; 691 | SWIFT_VERSION = 5.0; 692 | TARGETED_DEVICE_FAMILY = "1,2"; 693 | TEST_TARGET_NAME = iosApp; 694 | }; 695 | name = Release; 696 | }; 697 | /* End XCBuildConfiguration section */ 698 | 699 | /* Begin XCConfigurationList section */ 700 | 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = { 701 | isa = XCConfigurationList; 702 | buildConfigurations = ( 703 | 7555FFA3242A565B00829871 /* Debug */, 704 | 7555FFA4242A565B00829871 /* Release */, 705 | ); 706 | defaultConfigurationIsVisible = 0; 707 | defaultConfigurationName = Release; 708 | }; 709 | 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = { 710 | isa = XCConfigurationList; 711 | buildConfigurations = ( 712 | 7555FFA6242A565B00829871 /* Debug */, 713 | 7555FFA7242A565B00829871 /* Release */, 714 | ); 715 | defaultConfigurationIsVisible = 0; 716 | defaultConfigurationName = Release; 717 | }; 718 | 7555FFA8242A565B00829871 /* Build configuration list for PBXNativeTarget "iosAppTests" */ = { 719 | isa = XCConfigurationList; 720 | buildConfigurations = ( 721 | 7555FFA9242A565B00829871 /* Debug */, 722 | 7555FFAA242A565B00829871 /* Release */, 723 | ); 724 | defaultConfigurationIsVisible = 0; 725 | defaultConfigurationName = Release; 726 | }; 727 | 7555FFAB242A565B00829871 /* Build configuration list for PBXNativeTarget "iosAppUITests" */ = { 728 | isa = XCConfigurationList; 729 | buildConfigurations = ( 730 | 7555FFAC242A565B00829871 /* Debug */, 731 | 7555FFAD242A565B00829871 /* Release */, 732 | ); 733 | defaultConfigurationIsVisible = 0; 734 | defaultConfigurationName = Release; 735 | }; 736 | /* End XCConfigurationList section */ 737 | }; 738 | rootObject = 7555FF73242A565900829871 /* Project object */; 739 | } 740 | -------------------------------------------------------------------------------- /iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /iosApp/iosApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | var window: UIWindow? 7 | 8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 9 | // Override point for customization after application launch. 10 | return true 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/placeHolderImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "placeholder.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/placeHolderImage.imageset/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/iosApp/iosApp/Assets.xcassets/placeHolderImage.imageset/placeholder.jpg -------------------------------------------------------------------------------- /iosApp/iosApp/Colors.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iosApp/iosApp/Colors.xcassets/darkGrey.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.776", 9 | "green" : "0.627", 10 | "red" : "0.275" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "idiom" : "universal" 23 | } 24 | ], 25 | "info" : { 26 | "author" : "xcode", 27 | "version" : 1 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /iosApp/iosApp/Colors.xcassets/mainColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.294", 9 | "green" : "0.235", 10 | "red" : "0.106" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "idiom" : "universal" 23 | } 24 | ], 25 | "info" : { 26 | "author" : "xcode", 27 | "version" : 1 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /iosApp/iosApp/Colors.xcassets/navigationBarColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.490", 9 | "green" : "0.392", 10 | "red" : "0.176" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /iosApp/iosApp/Constants/Structures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constant Strings.swift 3 | // iosApp 4 | // 5 | // Created by Mohammed Hanif on 03/03/21. 6 | // Copyright © 2021 orgName. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ConstantStrings { 12 | static var movieCell = "moviesCell" 13 | static var votesOutOf = "/ 10" 14 | static var placeHolderImageName = "placeHolderImage" 15 | static var alertTitle = "Alert" 16 | static var ok = "ok" 17 | } 18 | -------------------------------------------------------------------------------- /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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UIStatusBarStyle 37 | UIStatusBarStyleLightContent 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /iosApp/iosApp/Storyboards/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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /iosApp/iosApp/Storyboards/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | -------------------------------------------------------------------------------- /iosApp/iosApp/ViewControllers/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.swift 3 | // iosApp 4 | // 5 | // Created by Mohammed Hanifbiji on 22/01/21. 6 | // Copyright © 2021 orgName. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import shared 11 | 12 | class MainViewController: UIViewController { 13 | 14 | // MARK: - Outlets 15 | @IBOutlet weak var tblViewMovies: UITableView! 16 | @IBOutlet weak var progressbar: UIActivityIndicatorView! 17 | 18 | // MARK: - Variables 19 | fileprivate var movieList : [MovieEntity] = [] 20 | fileprivate var viewModel: MainViewModel? 21 | 22 | // MARK: - UIViewController Life Cycle 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | viewModel = MainViewModel() 26 | viewModel?.getMovies() 27 | bindData() 28 | } 29 | 30 | /// Bind ViewModel and observe api callback 31 | fileprivate func bindData() { 32 | viewModel?.apiCallback = { [weak self] (movies, error) in 33 | guard let `self` = self else { 34 | return 35 | } 36 | self.progressbar.isHidden = true 37 | if error != nil { 38 | self.showAlert(message: error?.errorResponse?.statusMessage ?? "") 39 | } else { 40 | self.movieList = movies as? [MovieEntity] ?? [] 41 | self.tblViewMovies.reloadData() 42 | self.tblViewMovies.isHidden = false 43 | } 44 | } 45 | } 46 | 47 | /// Show Alert 48 | /// - Parameter message: Message received from Api. 49 | fileprivate func showAlert(message: String) { 50 | let alert = UIAlertController(title: ConstantStrings.alertTitle, message: message, preferredStyle: UIAlertController.Style.alert) 51 | alert.addAction(UIAlertAction(title: ConstantStrings.ok, style: UIAlertAction.Style.default, handler: nil)) 52 | self.present(alert, animated: true, completion: nil) 53 | } 54 | 55 | } 56 | 57 | // MARK: - UITableViewDataSource 58 | extension MainViewController: UITableViewDataSource { 59 | 60 | /// Use this to set number od rows we want in our tableView 61 | /// - Parameters: 62 | /// - tableView: UITableView 63 | /// - section: TableView Section 64 | /// - Returns: Count of rows present 65 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 66 | return movieList.count 67 | } 68 | 69 | /// Use this to set data in tableView Cell 70 | /// - Parameters: 71 | /// - tableView: UITableView 72 | /// - indexPath: indexpath 73 | /// - Returns: Returns our custom cell or UITableViewCell itself 74 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 75 | // TODO: Api call 76 | guard let tableViewCell = tableView.dequeueReusableCell(withIdentifier: ConstantStrings.movieCell, for: indexPath) as? MoviesCell else { 77 | return UITableViewCell() 78 | } 79 | let movie = movieList[indexPath.row] 80 | tableViewCell.setUpMoviesData(movieEntity: movie) 81 | return tableViewCell 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /iosApp/iosApp/ViewControllers/MainViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewModel.swift 3 | // iosApp 4 | // 5 | // Created by Mohammed Hanif on 05/03/21. 6 | // Copyright © 2021 orgName. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import shared 11 | 12 | class MainViewModel { 13 | 14 | // MARK: - Variables 15 | var apiCallback: ((_ movies: NSMutableArray?, _ error: CustomException?) -> Void)? 16 | 17 | /// Get Movies data from Api 18 | func getMovies() { 19 | BaseApiClass().getMovies { (succes, error) in 20 | if error != nil { 21 | return 22 | } 23 | succes?.fold(failed: { (exception) in 24 | self.apiCallback?(nil, exception) 25 | }, succeeded: { (popularMovies) -> Any? in 26 | self.apiCallback?(popularMovies?.results, nil) 27 | return nil 28 | }) 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /iosApp/iosApp/ViewControllers/MoviesCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviesCell.swift 3 | // iosApp 4 | // 5 | // Created by Mohammed Hanif on 20/02/21. 6 | // Copyright © 2021 orgName. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import shared 11 | import Kingfisher 12 | 13 | class MoviesCell: UITableViewCell { 14 | 15 | // MARK: - Outlets 16 | @IBOutlet weak var lblMovieName: UILabel! 17 | @IBOutlet weak var lblMovieRating: UILabel! 18 | @IBOutlet weak var lblMovieOverView: UILabel! 19 | @IBOutlet weak var imgMoviePoster: UIImageView! 20 | 21 | // MARK: - SetUp Movie Details 22 | func setUpMoviesData(movieEntity: MovieEntity) { 23 | lblMovieName.text = movieEntity.title 24 | lblMovieRating.text = "\(movieEntity.voteAverage) \(ConstantStrings.votesOutOf)" 25 | lblMovieOverView.text = movieEntity.overview 26 | imgMoviePoster.kf.setImage(with: URL(string: movieEntity.picturePoster), placeholder:UIImage(named: ConstantStrings.placeHolderImageName)) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /iosApp/iosAppTests/iosAppTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import iosApp 3 | 4 | class iosAppTests: XCTestCase { 5 | 6 | override func setUp() { 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | } 13 | 14 | func testExample() { 15 | // This is an example of a functional test case. 16 | // Use XCTAssert and related functions to verify your tests produce the correct results. 17 | } 18 | 19 | func testPerformanceExample() { 20 | // This is an example of a performance test case. 21 | self.measure { 22 | // Put the code you want to measure the time of here. 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /iosApp/iosAppUITests/iosAppUITests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | class appNameUITests: XCTestCase { 4 | 5 | override func setUp() { 6 | // Put setup code here. This method is called before the invocation of each test method in the class. 7 | 8 | // In UI tests it is usually best to stop immediately when a failure occurs. 9 | continueAfterFailure = false 10 | 11 | // 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. 12 | } 13 | 14 | override func tearDown() { 15 | // Put teardown code here. This method is called after the invocation of each test method in the class. 16 | } 17 | 18 | func testExample() { 19 | // UI tests must launch the application that they test. 20 | let app = XCUIApplication() 21 | app.launch() 22 | 23 | // Use recording to get started writing UI tests. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testLaunchPerformance() { 28 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 29 | // This measures how long it takes to launch your application. 30 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { 31 | XCUIApplication().launch() 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /screenshots/KMM Plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/screenshots/KMM Plugin.png -------------------------------------------------------------------------------- /screenshots/Run Configurations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/screenshots/Run Configurations.png -------------------------------------------------------------------------------- /screenshots/android.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/screenshots/android.gif -------------------------------------------------------------------------------- /screenshots/iOS.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/screenshots/iOS.gif -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | jcenter() 5 | gradlePluginPortal() 6 | mavenCentral() 7 | } 8 | 9 | } 10 | rootProject.name = "KMMSample" 11 | 12 | 13 | include(":androidApp") 14 | include(":shared") 15 | 16 | -------------------------------------------------------------------------------- /shared/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 2 | 3 | plugins { 4 | kotlin(SharedPlugins.MULTIPLATFORM) 5 | kotlin(SharedPlugins.SERIALIZATION) 6 | id(SharedPlugins.SHARED_MODULE) 7 | } 8 | 9 | kotlin { 10 | android() 11 | ios { 12 | binaries { 13 | framework { 14 | baseName = "shared" 15 | } 16 | } 17 | } 18 | sourceSets { 19 | val commonMain by getting { 20 | dependencies { 21 | // Coroutines 22 | implementation(SharedLibs.COROUINES_CORE) 23 | //Ktor features 24 | implementation(SharedLibs.KTOR) 25 | implementation(SharedLibs.KTOR_SERIALIZER) 26 | // KotlinX Serialization 27 | implementation(SharedLibs.KOTLINX_SERIALIZATION) 28 | } 29 | } 30 | val commonTest by getting { 31 | dependencies { 32 | implementation(kotlin("test-common")) 33 | implementation(kotlin("test-annotations-common")) 34 | } 35 | } 36 | val androidMain by getting { 37 | dependencies { 38 | // Ktor and it's features 39 | implementation(SharedLibs.KTOR_ANDROID) 40 | implementation(SharedLibs.KTOR_JSON_ANDROID) 41 | implementation(SharedLibs.KTOR_SERIALIZER_ANDROID) 42 | // Kotlinx serializer 43 | implementation(SharedLibs.KOTLINX_SERIALIZER_ANDROID) 44 | } 45 | } 46 | val androidTest by getting { 47 | dependencies { 48 | implementation(kotlin("test-junit")) 49 | implementation("junit:junit:4.13") 50 | } 51 | } 52 | val iosMain by getting { 53 | dependencies { 54 | //Ktor 55 | implementation(SharedLibs.KTOR_IOS) 56 | } 57 | } 58 | val iosTest by getting 59 | } 60 | } 61 | 62 | android { 63 | compileSdkVersion(Version.COMPILE_SDK) 64 | sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") 65 | defaultConfig { 66 | minSdkVersion(Version.MIN_SDK) 67 | targetSdkVersion(Version.TARGET_SDK) 68 | } 69 | } 70 | 71 | val packForXcode by tasks.creating(Sync::class) { 72 | group = "build" 73 | val mode = System.getenv("CONFIGURATION") ?: "DEBUG" 74 | val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator" 75 | val targetName = "ios" + if (sdkName.startsWith("iphoneos")) "Arm64" else "X64" 76 | val framework = kotlin.targets.getByName(targetName).binaries.getFramework(mode) 77 | inputs.property("mode", mode) 78 | dependsOn(framework.linkTask) 79 | val targetDir = File(buildDir, "xcode-frameworks") 80 | from({ framework.outputDirectory }) 81 | into(targetDir) 82 | } 83 | 84 | tasks.getByName("build").dependsOn(packForXcode) -------------------------------------------------------------------------------- /shared/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/com/simform/kmmsample/shared/HttpBaseClient.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.shared 2 | 3 | import com.simform.kmmsample.shared.datamodels.base.CustomException 4 | import com.simform.kmmsample.shared.datamodels.base.ErrorResponse 5 | import com.simform.kmmsample.shared.utils.ApiEndPoints 6 | import com.simform.kmmsample.shared.utils.CLIENT_REQUEST_EXCEPTION_RANGE 7 | import com.simform.kmmsample.shared.utils.REDIRECT_RESPONSE_EXCEPTION_RANGE 8 | import com.simform.kmmsample.shared.utils.RESPONSE_EXCEPTION_CODE 9 | import com.simform.kmmsample.shared.utils.SERVER_RESPONSE_EXCEPTION_RANGE 10 | import io.ktor.client.HttpClient 11 | import io.ktor.client.call.receive 12 | import io.ktor.client.features.ClientRequestException 13 | import io.ktor.client.features.HttpResponseValidator 14 | import io.ktor.client.features.RedirectResponseException 15 | import io.ktor.client.features.ResponseException 16 | import io.ktor.client.features.ServerResponseException 17 | import io.ktor.client.features.defaultRequest 18 | import io.ktor.client.features.json.JsonFeature 19 | import io.ktor.client.features.json.serializer.KotlinxSerializer 20 | import io.ktor.client.request.host 21 | import io.ktor.http.ContentType 22 | import io.ktor.http.contentType 23 | 24 | actual class HttpBaseClient { 25 | actual val httpClient: HttpClient = HttpClient { 26 | defaultRequest { 27 | host = ApiEndPoints.BASE.url 28 | contentType(ContentType.Application.Json) 29 | } 30 | // Validate Response 31 | expectSuccess = false 32 | // JSON Deserializer 33 | install(JsonFeature) { 34 | val json = kotlinx.serialization.json.Json { 35 | ignoreUnknownKeys = true 36 | coerceInputValues = true 37 | } 38 | serializer = KotlinxSerializer(json) 39 | } 40 | HttpResponseValidator { 41 | validateResponse { response -> 42 | when (response.status.value) { 43 | in REDIRECT_RESPONSE_EXCEPTION_RANGE -> throw RedirectResponseException(response, "") 44 | in CLIENT_REQUEST_EXCEPTION_RANGE -> throw ClientRequestException(response, "") 45 | in SERVER_RESPONSE_EXCEPTION_RANGE -> throw ServerResponseException(response, "") 46 | } 47 | if (response.status.value >= RESPONSE_EXCEPTION_CODE) { 48 | throw ResponseException(response, "") 49 | } 50 | } 51 | handleResponseException { cause -> 52 | var error = ErrorResponse() 53 | when (cause) { 54 | is ResponseException -> { 55 | error = cause.response.receive() 56 | error.statusCode = cause.response.status.value 57 | } 58 | is java.net.UnknownHostException -> { 59 | error = CustomException.getNoInternetError() 60 | } 61 | else -> CustomException.getDefaultError(cause.message) 62 | } 63 | throw CustomException(error) 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/simform/kmmsample/shared/HttpBaseClient.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.shared 2 | 3 | import io.ktor.client.HttpClient 4 | 5 | expect class HttpBaseClient() { 6 | val httpClient: HttpClient 7 | } 8 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/simform/kmmsample/shared/datamodels/base/CustomException.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.shared.datamodels.base 2 | 3 | @Suppress("TooGenericExceptionCaught") 4 | class CustomException(var errorResponse: ErrorResponse?) : Exception() { 5 | 6 | companion object { 7 | 8 | fun getDefaultError(errorMessage: String?): ErrorResponse { 9 | return ErrorResponse(1, "SOMETHING_WENT_WRONG", false) 10 | } 11 | 12 | fun getNoInternetError(): ErrorResponse { 13 | return ErrorResponse(1, "NO_INTERNET_CONNECTION", false) 14 | } 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/simform/kmmsample/shared/datamodels/base/Either.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.shared.datamodels.base 2 | 3 | // This class will be used to handle failure and Success response from the api. 4 | sealed class Either { 5 | // Execute failure block if we receive any response from api else will execute success block in which we can get our data 6 | inline fun fold(failed: (F) -> T, succeeded: (S) -> T): T = 7 | when (this) { 8 | is Failure -> failed(failure) 9 | is Success -> succeeded(success) 10 | } 11 | } 12 | 13 | data class Failure(val failure: F) : Either() 14 | 15 | data class Success(val success: S) : Either() -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/simform/kmmsample/shared/datamodels/base/ErrorResponse.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.shared.datamodels.base 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | // Custom Error class according to error thrown by api 7 | @Serializable 8 | data class ErrorResponse ( 9 | var statusCode: Int? = 0, 10 | @SerialName("status_message") 11 | var statusMessage: String? = "", 12 | var success: Boolean? = false 13 | ) -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/simform/kmmsample/shared/datamodels/responsemodels/MovieEnitity.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.shared.datamodels.responsemodels 2 | 3 | import com.simform.kmmsample.shared.utils.POSTER_URL 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @Serializable 8 | data class MovieEntity( 9 | val popularity: Double, 10 | val id: Int, 11 | val video: Boolean, 12 | @SerialName("vote_count") val voteCount: Int, 13 | @SerialName("vote_average") val voteAverage: Double, 14 | val title: String, 15 | @SerialName("release_date") val releaseDate: String, 16 | @SerialName("original_language") val originalLanguage: String, 17 | @SerialName("original_title") val originalTitle: String, 18 | @SerialName("genre_ids") val genreIds: List, 19 | @SerialName("backdrop_path") val backdropPath: String, 20 | val adult: Boolean, 21 | val overview: String, 22 | @SerialName("poster_path") var posterPath: String 23 | ) { 24 | val picturePoster: String get() = POSTER_URL + posterPath 25 | } 26 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/simform/kmmsample/shared/datamodels/responsemodels/PopularMoviesResponse.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.shared.datamodels.responsemodels 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class PopularMoviesResponse ( 7 | val page: Int, 8 | val results: ArrayList, 9 | val total_pages: Int, 10 | val total_results: Int 11 | ) 12 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/simform/kmmsample/shared/remote/BaseApiClass.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.shared.remote 2 | 3 | import com.simform.kmmsample.shared.HttpBaseClient 4 | import com.simform.kmmsample.shared.datamodels.base.CustomException 5 | import com.simform.kmmsample.shared.datamodels.base.Either 6 | import com.simform.kmmsample.shared.datamodels.base.Failure 7 | import com.simform.kmmsample.shared.datamodels.base.Success 8 | import com.simform.kmmsample.shared.datamodels.responsemodels.PopularMoviesResponse 9 | import com.simform.kmmsample.shared.utils.API_KEY 10 | import com.simform.kmmsample.shared.utils.ApiEndPoints 11 | import com.simform.kmmsample.shared.utils.HEADER_AUTHORIZATION 12 | import io.ktor.client.request.get 13 | import io.ktor.client.request.parameter 14 | 15 | class BaseApiClass { 16 | 17 | //Create Http Client 18 | private var httpClient = HttpBaseClient().httpClient 19 | 20 | @Throws(Exception::class) 21 | suspend fun getMovies() : Either? { 22 | return try { 23 | val response = httpClient.get(ApiEndPoints.POPULAR_MOVIES.url) { 24 | parameter(HEADER_AUTHORIZATION, API_KEY) 25 | } 26 | Success(response) 27 | } catch (e: Exception) { 28 | Failure(e as CustomException) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/simform/kmmsample/shared/utils/ApiEndPoints.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.shared.utils 2 | 3 | enum class ApiEndPoints(val url: String) { 4 | BASE(url = "api.themoviedb.org"), POPULAR_MOVIES("3/movie/popular") 5 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/simform/kmmsample/shared/utils/SharedConstants.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.shared.utils 2 | 3 | //Error response codes 4 | val REDIRECT_RESPONSE_EXCEPTION_RANGE = 300..399 5 | val CLIENT_REQUEST_EXCEPTION_RANGE = 400..499 6 | val SERVER_RESPONSE_EXCEPTION_RANGE = 500..599 7 | const val RESPONSE_EXCEPTION_CODE = 600 8 | const val UNAUTHORIZED_RESPONSE_CODE = 401 9 | 10 | // Movie model 11 | const val POSTER_URL = "https://image.tmdb.org/t/p/original" 12 | const val HEADER_AUTHORIZATION = "api_key" 13 | // Please add your private api key here 14 | const val API_KEY = "" -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/com/simform/kmmsample/shared/HttpBaseClient.kt: -------------------------------------------------------------------------------- 1 | package com.simform.kmmsample.shared 2 | 3 | import com.simform.kmmsample.shared.datamodels.base.CustomException 4 | import com.simform.kmmsample.shared.datamodels.base.ErrorResponse 5 | import com.simform.kmmsample.shared.utils.ApiEndPoints 6 | import com.simform.kmmsample.shared.utils.CLIENT_REQUEST_EXCEPTION_RANGE 7 | import com.simform.kmmsample.shared.utils.REDIRECT_RESPONSE_EXCEPTION_RANGE 8 | import com.simform.kmmsample.shared.utils.RESPONSE_EXCEPTION_CODE 9 | import com.simform.kmmsample.shared.utils.SERVER_RESPONSE_EXCEPTION_RANGE 10 | import io.ktor.client.HttpClient 11 | import io.ktor.client.call.receive 12 | import io.ktor.client.engine.ios.Ios 13 | import io.ktor.client.engine.ios.IosHttpRequestException 14 | import io.ktor.client.features.ClientRequestException 15 | import io.ktor.client.features.HttpResponseValidator 16 | import io.ktor.client.features.RedirectResponseException 17 | import io.ktor.client.features.ResponseException 18 | import io.ktor.client.features.ServerResponseException 19 | import io.ktor.client.features.defaultRequest 20 | import io.ktor.client.features.json.JsonFeature 21 | import io.ktor.client.features.json.serializer.KotlinxSerializer 22 | import io.ktor.client.request.host 23 | import io.ktor.http.ContentType 24 | import io.ktor.http.contentType 25 | import platform.Foundation.setHTTPShouldUsePipelining 26 | 27 | actual class HttpBaseClient { 28 | actual val httpClient: HttpClient = HttpClient(Ios) { 29 | defaultRequest { 30 | host = ApiEndPoints.BASE.url 31 | contentType(ContentType.Application.Json) 32 | } 33 | 34 | engine { 35 | // this: IosClientEngineConfig 36 | configureRequest { 37 | setHTTPShouldUsePipelining(true) 38 | } 39 | } 40 | 41 | // Validate Response 42 | expectSuccess = false 43 | // JSON Deserializer 44 | install(JsonFeature) { 45 | val json = kotlinx.serialization.json.Json { 46 | ignoreUnknownKeys = true 47 | coerceInputValues = true 48 | } 49 | serializer = KotlinxSerializer(json) 50 | } 51 | 52 | HttpResponseValidator { 53 | validateResponse { response -> 54 | when (response.status.value) { 55 | in REDIRECT_RESPONSE_EXCEPTION_RANGE -> throw RedirectResponseException(response, "") 56 | in CLIENT_REQUEST_EXCEPTION_RANGE -> throw ClientRequestException(response, "") 57 | in SERVER_RESPONSE_EXCEPTION_RANGE -> throw ServerResponseException(response, "") 58 | } 59 | if (response.status.value >= RESPONSE_EXCEPTION_CODE) { 60 | throw ResponseException(response, "") 61 | } 62 | } 63 | handleResponseException { cause -> 64 | var error = ErrorResponse() 65 | when (cause) { 66 | is ResponseException -> { 67 | error = cause.response.receive() 68 | error.statusCode = cause.response.status.value 69 | } 70 | is IosHttpRequestException -> { 71 | CustomException.getNoInternetError() 72 | } 73 | else -> CustomException.getDefaultError(cause.message) 74 | } 75 | throw CustomException(error) 76 | } 77 | } 78 | } 79 | } --------------------------------------------------------------------------------