├── .github ├── ISSUE_TEMPLATE │ ├── add-repository.md │ ├── get-data-from-the-internet.md │ ├── load-and-display-images.md │ └── project-bookshelf.md ├── renovate.json └── workflows │ └── main.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── marsphotos │ │ │ ├── MainActivity.kt │ │ │ ├── MarsPhotosApplication.kt │ │ │ ├── data │ │ │ ├── AppContainer.kt │ │ │ └── MarsPhotosRepository.kt │ │ │ ├── model │ │ │ └── MarsPhoto.kt │ │ │ ├── network │ │ │ └── MarsApiService.kt │ │ │ └── ui │ │ │ ├── MarsPhotosApp.kt │ │ │ ├── screens │ │ │ ├── HomeScreen.kt │ │ │ └── MarsViewModel.kt │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Shape.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable │ │ ├── ic_broken_image.xml │ │ ├── ic_connection_error.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_foreground.xml │ │ └── loading_img.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── example │ └── marsphotos │ ├── MarsViewModelTest.kt │ ├── NetworkMarsRepositoryTest.kt │ ├── fake │ ├── FakeDataSource.kt │ ├── FakeMarsApiService.kt │ └── FakeNetworkMarsPhotosRepository.kt │ └── rules │ └── TestDispatcherRule.kt ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/ISSUE_TEMPLATE/add-repository.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Add Repository issue template. 3 | about: Issue template for Add Repository codelab 4 | title: Add Repository 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **URL of codelab:** 11 | 12 | 13 | **Specify the language of the codelab if it is not English:** 14 | 15 | 16 | 17 | **In which task and step of the codelab can this issue be found?** 18 | 19 | 20 | 21 | **Describe the problem** 22 | 23 | 24 | 25 | 26 | **Steps to reproduce?** 27 | 1. Go to... 28 | 2. Click on... 29 | 3. See error... 30 | 31 | **Versions** 32 | _Android Studio version:_ 33 | _API version of the emulator:_ 34 | 35 | 36 | **Additional information** 37 | _Include screenshots if they would be useful in clarifying the problem._ 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/get-data-from-the-internet.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Get Data from the Internet issue template 3 | about: Issue template for Get Data From the Internet codelab 4 | title: Get Data from the Internet 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **URL of codelab:** 11 | 12 | 13 | **Specify the language of the codelab if it is not English:** 14 | 15 | 16 | 17 | **In which task and step of the codelab can this issue be found?** 18 | 19 | 20 | **Describe the problem** 21 | 22 | 23 | 24 | 25 | **Steps to reproduce?** 26 | 1. Go to... 27 | 2. Click on... 28 | 3. See error... 29 | 30 | **Versions** 31 | _Android Studio version:_ 32 | _API version of the emulator:_ 33 | 34 | 35 | **Additional information** 36 | _Include screenshots if they would be useful in clarifying the problem._ 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/load-and-display-images.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Load and Display Images from the Internet issue template 3 | about: Issue template for Load and Display Images from the Internet codelab 4 | title: Load and Display Images from the Internet 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **URL of codelab:** 11 | 12 | 13 | **Specify the language of the codelab if it is not English:** 14 | 15 | 16 | 17 | **In which task and step of the codelab can this issue be found?** 18 | 19 | 20 | **Describe the problem** 21 | 22 | 23 | 24 | 25 | **Steps to reproduce?** 26 | 1. Go to... 27 | 2. Click on... 28 | 3. See error... 29 | 30 | **Versions** 31 | _Android Studio version:_ 32 | _API version of the emulator:_ 33 | 34 | 35 | **Additional information** 36 | _Include screenshots if they would be useful in clarifying the problem._ 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/project-bookshelf.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Create a Bookshelf App 3 | about: Issue template for Create a Bookshelf App project 4 | title: Create a Bookshelf App 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **URL of codelab:** 11 | 12 | 13 | **Specify the language of the codelab if it is not English:** 14 | 15 | 16 | 17 | **In which task and step of the codelab can this issue be found?** 18 | 19 | 20 | **Describe the problem** 21 | 22 | 23 | 24 | 25 | **Steps to reproduce?** 26 | 1. Go to... 27 | 2. Click on... 28 | 3. See error... 29 | 30 | **Versions** 31 | _Android Studio version:_ 32 | _API version of the emulator:_ 33 | 34 | 35 | **Additional information** 36 | _Include screenshots if they would be useful in clarifying the problem._ 37 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>android/.github:renovate-config" 5 | ], 6 | 7 | "baseBranches": [ 8 | "main", 9 | "starter" 10 | ] 11 | 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - starter 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Set Up JDK 16 | uses: actions/setup-java@v4 17 | with: 18 | distribution: 'zulu' # See 'Supported distributions' for available options 19 | java-version: '17' 20 | cache: 'gradle' 21 | 22 | - name: Setup Gradle 23 | uses: gradle/actions/setup-gradle@v4 24 | 25 | - name: Setup Android SDK 26 | uses: android-actions/setup-android@v3 27 | 28 | - name: Build project and run local and device tests 29 | run: ./gradlew :app:assembleDebug 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Mac files 6 | .DS_Store 7 | 8 | # files for the dex VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # generated files 15 | bin/ 16 | gen/ 17 | 18 | # Ignore gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | proguard-project.txt 28 | 29 | # Eclipse files 30 | .project 31 | .classpath 32 | .settings/ 33 | 34 | # Android Studio/IDEA 35 | *.iml 36 | .idea 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your patches! Before we can take them, we have to jump a couple of 6 | legal hurdles. 7 | 8 | Please fill out either the individual or corporate Contributor License Agreement (CLA). 9 | 10 | * If you are an individual writing original source code and you're sure you 11 | own the intellectual property, then you'll need to sign an [individual CLA] 12 | (https://cla.developers.google.com). 13 | * If you work for a company that wants to allow you to contribute your work, 14 | then you'll need to sign a [corporate CLA] 15 | (https://cla.developers.google.com). 16 | * Please make sure you sign both, Android and Google CLA 17 | 18 | Follow either of the two links above to access the appropriate CLA and 19 | instructions for how to sign and return it. Once we receive it, we'll be able to 20 | accept your pull requests. 21 | 22 | ## Contributing A Patch 23 | 24 | 1. Submit an issue describing your proposed change to the repo in question. 25 | 1. The repo owner will respond to your issue promptly. 26 | 1. If your proposed change is accepted, and you haven't already done so, sign a 27 | Contributor License Agreement (see details above). 28 | 1. Fork the desired repo, develop and test your code changes. 29 | 1. Ensure that your code adheres to the existing style in the sample to which 30 | you are contributing. Refer to the 31 | [Android Code Style Guide] 32 | (https://source.android.com/source/code-style.html) for the 33 | recommended coding standards for this organization. 34 | 1. Ensure that your code has an appropriate set of unit tests which all pass. 35 | 1. Submit a pull request. 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Mars Photos 2 | ================================== 3 | 4 | Mars Photos app is a demo app that shows actual images of Mars' surface. These images are 5 | real-life photos from Mars captured by NASA's Mars rovers. The data is stored on a Web server 6 | as a REST web service. 7 | 8 | This app demonstrated the use of [Retrofit](https://square.github.io/retrofit/) to make REST requests to the web service, [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) to 9 | handle the deserialization of the returned JSON to Kotlin data objects, and [Coil](https://coil-kt.github.io/coil/) to load images by URL. 10 | 11 | Pre-requisites 12 | -------------- 13 | 14 | You need to know: 15 | - How to create Composable functions. 16 | - How to use architecture components including ViewModel. 17 | - How to use coroutines for long-running tasks. 18 | - Familiarity with lazy grid 19 | 20 | Getting Started 21 | --------------- 22 | 23 | 1. [Install Android Studio](https://developer.android.com/studio/install.html), if you don't already 24 | have it. 25 | 2. Download the sample. 26 | 3. Import the sample into Android Studio. 27 | 4. Build and run the sample. 28 | 29 | ## [Build this app through hands-on codelabs in the Android Basics with Compose Course](https://developer.android.com/courses/android-basics-compose/course) 30 | 31 | ### [Get data from the internet](https://developer.android.com/codelabs/basic-android-kotlin-compose-getting-data-internet) 32 | Learn how to use community-developed libraries to connect to a web service to retrieve and display data in your Android Kotlin compose app. 33 | 34 | ### [Add repository and Manual DI](https://developer.android.com/codelabs/basic-android-kotlin-compose-add-repository) 35 | Learn how to improve the architecture of the app by separating the network calls into a repository. 36 | 37 | ### [Load and display images from the internet](https://developer.android.com/codelabs/basic-android-kotlin-compose-load-images) 38 | Use the Coil library to load and display photos from the internet in your Android Compose app. 39 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id("com.android.application") 19 | id("org.jetbrains.kotlin.android") 20 | id("org.jetbrains.kotlin.plugin.serialization") 21 | id("org.jetbrains.kotlin.plugin.compose") 22 | } 23 | 24 | android { 25 | namespace = "com.example.marsphotos" 26 | compileSdk = 35 27 | 28 | defaultConfig { 29 | applicationId = "com.example.marsphotos" 30 | minSdk = 24 31 | targetSdk = 35 32 | versionCode = 1 33 | versionName = "1.0" 34 | 35 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 36 | vectorDrawables { 37 | useSupportLibrary = true 38 | } 39 | } 40 | 41 | buildTypes { 42 | release { 43 | isMinifyEnabled = false 44 | proguardFiles( 45 | getDefaultProguardFile("proguard-android-optimize.txt"), 46 | "proguard-rules.pro" 47 | ) 48 | } 49 | } 50 | compileOptions { 51 | sourceCompatibility = JavaVersion.VERSION_1_8 52 | targetCompatibility = JavaVersion.VERSION_1_8 53 | } 54 | kotlinOptions { 55 | jvmTarget = "1.8" 56 | } 57 | buildFeatures { 58 | compose = true 59 | } 60 | packaging { 61 | resources { 62 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 63 | } 64 | } 65 | } 66 | 67 | dependencies { 68 | 69 | // Import the Compose BOM 70 | implementation(platform("androidx.compose:compose-bom:2024.12.01")) 71 | implementation("androidx.activity:activity-compose:1.9.3") 72 | implementation("androidx.compose.material3:material3") 73 | implementation("androidx.compose.ui:ui") 74 | implementation("androidx.compose.ui:ui-tooling-preview") 75 | implementation("androidx.core:core-ktx:1.15.0") 76 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") 77 | implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7") 78 | 79 | // Retrofit 80 | implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") 81 | implementation("com.squareup.retrofit2:retrofit:2.11.0") 82 | implementation("io.coil-kt:coil-compose:2.7.0") 83 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0") 84 | 85 | testImplementation("junit:junit:4.13.2") 86 | testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.1") 87 | 88 | androidTestImplementation(platform("androidx.compose:compose-bom:2024.12.01")) 89 | androidTestImplementation("androidx.compose.ui:ui-test-junit4") 90 | androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") 91 | androidTestImplementation("androidx.test.ext:junit:1.2.1") 92 | 93 | debugImplementation("androidx.compose.ui:ui-test-manifest") 94 | debugImplementation("androidx.compose.ui:ui-tooling") 95 | } 96 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 21 | 30 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/marsphotos/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.marsphotos 18 | 19 | import android.os.Bundle 20 | import androidx.activity.ComponentActivity 21 | import androidx.activity.compose.setContent 22 | import androidx.activity.enableEdgeToEdge 23 | import androidx.compose.foundation.layout.fillMaxSize 24 | import androidx.compose.material3.Surface 25 | import androidx.compose.ui.Modifier 26 | import com.example.marsphotos.ui.MarsPhotosApp 27 | import com.example.marsphotos.ui.theme.MarsPhotosTheme 28 | 29 | class MainActivity : ComponentActivity() { 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | enableEdgeToEdge() 32 | super.onCreate(savedInstanceState) 33 | setContent { 34 | MarsPhotosTheme { 35 | Surface( 36 | modifier = Modifier.fillMaxSize(), 37 | ) { 38 | MarsPhotosApp() 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/marsphotos/MarsPhotosApplication.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.marsphotos 17 | 18 | import android.app.Application 19 | import com.example.marsphotos.data.AppContainer 20 | import com.example.marsphotos.data.DefaultAppContainer 21 | 22 | class MarsPhotosApplication : Application() { 23 | /** AppContainer instance used by the rest of classes to obtain dependencies */ 24 | lateinit var container: AppContainer 25 | override fun onCreate() { 26 | super.onCreate() 27 | container = DefaultAppContainer() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/marsphotos/data/AppContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.marsphotos.data 17 | 18 | import com.example.marsphotos.network.MarsApiService 19 | import retrofit2.Retrofit 20 | import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory 21 | import kotlinx.serialization.json.Json 22 | import okhttp3.MediaType.Companion.toMediaType 23 | 24 | /** 25 | * Dependency Injection container at the application level. 26 | */ 27 | interface AppContainer { 28 | val marsPhotosRepository: MarsPhotosRepository 29 | } 30 | 31 | /** 32 | * Implementation for the Dependency Injection container at the application level. 33 | * 34 | * Variables are initialized lazily and the same instance is shared across the whole app. 35 | */ 36 | class DefaultAppContainer : AppContainer { 37 | private val baseUrl = "https://android-kotlin-fun-mars-server.appspot.com/" 38 | 39 | /** 40 | * Use the Retrofit builder to build a retrofit object using a kotlinx.serialization converter 41 | */ 42 | private val retrofit: Retrofit = Retrofit.Builder() 43 | .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) 44 | .baseUrl(baseUrl) 45 | .build() 46 | 47 | /** 48 | * Retrofit service object for creating api calls 49 | */ 50 | private val retrofitService: MarsApiService by lazy { 51 | retrofit.create(MarsApiService::class.java) 52 | } 53 | 54 | /** 55 | * DI implementation for Mars photos repository 56 | */ 57 | override val marsPhotosRepository: MarsPhotosRepository by lazy { 58 | NetworkMarsPhotosRepository(retrofitService) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/marsphotos/data/MarsPhotosRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.marsphotos.data 17 | 18 | import com.example.marsphotos.model.MarsPhoto 19 | import com.example.marsphotos.network.MarsApiService 20 | 21 | /** 22 | * Repository that fetch mars photos list from marsApi. 23 | */ 24 | interface MarsPhotosRepository { 25 | /** Fetches list of MarsPhoto from marsApi */ 26 | suspend fun getMarsPhotos(): List 27 | } 28 | 29 | /** 30 | * Network Implementation of Repository that fetch mars photos list from marsApi. 31 | */ 32 | class NetworkMarsPhotosRepository( 33 | private val marsApiService: MarsApiService 34 | ) : MarsPhotosRepository { 35 | /** Fetches list of MarsPhoto from marsApi*/ 36 | override suspend fun getMarsPhotos(): List = marsApiService.getPhotos() 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/marsphotos/model/MarsPhoto.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.marsphotos.model 18 | 19 | import kotlinx.serialization.SerialName 20 | import kotlinx.serialization.Serializable 21 | 22 | /** 23 | * This data class defines a Mars photo which includes an ID, and the image URL. 24 | */ 25 | @Serializable 26 | data class MarsPhoto( 27 | val id: String, 28 | @SerialName(value = "img_src") 29 | val imgSrc: String 30 | ) 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/marsphotos/network/MarsApiService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.marsphotos.network 18 | 19 | import com.example.marsphotos.model.MarsPhoto 20 | import retrofit2.http.GET 21 | 22 | /** 23 | * A public interface that exposes the [getPhotos] method 24 | */ 25 | interface MarsApiService { 26 | /** 27 | * Returns a [List] of [MarsPhoto] and this method can be called from a Coroutine. 28 | * The @GET annotation indicates that the "photos" endpoint will be requested with the GET 29 | * HTTP method 30 | */ 31 | @GET("photos") 32 | suspend fun getPhotos(): List 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/marsphotos/ui/MarsPhotosApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:OptIn(ExperimentalMaterial3Api::class) 18 | 19 | package com.example.marsphotos.ui 20 | 21 | import androidx.compose.foundation.layout.fillMaxSize 22 | import androidx.compose.material3.CenterAlignedTopAppBar 23 | import androidx.compose.material3.ExperimentalMaterial3Api 24 | import androidx.compose.material3.MaterialTheme 25 | import androidx.compose.material3.Scaffold 26 | import androidx.compose.material3.Surface 27 | import androidx.compose.material3.Text 28 | import androidx.compose.material3.TopAppBarDefaults 29 | import androidx.compose.material3.TopAppBarScrollBehavior 30 | import androidx.compose.runtime.Composable 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.input.nestedscroll.nestedScroll 33 | import androidx.compose.ui.res.stringResource 34 | import androidx.lifecycle.viewmodel.compose.viewModel 35 | import com.example.marsphotos.R 36 | import com.example.marsphotos.ui.screens.HomeScreen 37 | import com.example.marsphotos.ui.screens.MarsViewModel 38 | 39 | @Composable 40 | fun MarsPhotosApp() { 41 | val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() 42 | Scaffold( 43 | modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), 44 | topBar = { MarsTopAppBar(scrollBehavior = scrollBehavior) } 45 | ) { 46 | Surface( 47 | modifier = Modifier.fillMaxSize() 48 | ) { 49 | val marsViewModel: MarsViewModel = 50 | viewModel(factory = MarsViewModel.Factory) 51 | HomeScreen( 52 | marsUiState = marsViewModel.marsUiState, 53 | retryAction = marsViewModel::getMarsPhotos, 54 | contentPadding = it 55 | ) 56 | } 57 | } 58 | } 59 | 60 | @Composable 61 | fun MarsTopAppBar(scrollBehavior: TopAppBarScrollBehavior, modifier: Modifier = Modifier) { 62 | CenterAlignedTopAppBar( 63 | scrollBehavior = scrollBehavior, 64 | title = { 65 | Text( 66 | text = stringResource(R.string.app_name), 67 | style = MaterialTheme.typography.headlineSmall, 68 | ) 69 | }, 70 | modifier = modifier 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/marsphotos/ui/screens/HomeScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.marsphotos.ui.screens 17 | 18 | import androidx.compose.foundation.Image 19 | import androidx.compose.foundation.layout.Arrangement 20 | import androidx.compose.foundation.layout.Column 21 | import androidx.compose.foundation.layout.PaddingValues 22 | import androidx.compose.foundation.layout.aspectRatio 23 | import androidx.compose.foundation.layout.fillMaxSize 24 | import androidx.compose.foundation.layout.fillMaxWidth 25 | import androidx.compose.foundation.layout.padding 26 | import androidx.compose.foundation.layout.size 27 | import androidx.compose.foundation.lazy.grid.GridCells 28 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid 29 | import androidx.compose.foundation.lazy.grid.items 30 | import androidx.compose.material3.Button 31 | import androidx.compose.material3.Card 32 | import androidx.compose.material3.CardDefaults 33 | import androidx.compose.material3.MaterialTheme 34 | import androidx.compose.material3.Text 35 | import androidx.compose.runtime.Composable 36 | import androidx.compose.ui.Alignment 37 | import androidx.compose.ui.Modifier 38 | import androidx.compose.ui.layout.ContentScale 39 | import androidx.compose.ui.platform.LocalContext 40 | import androidx.compose.ui.res.painterResource 41 | import androidx.compose.ui.res.stringResource 42 | import androidx.compose.ui.tooling.preview.Preview 43 | import androidx.compose.ui.unit.dp 44 | import coil.compose.AsyncImage 45 | import coil.request.ImageRequest 46 | import com.example.marsphotos.R 47 | import com.example.marsphotos.model.MarsPhoto 48 | import com.example.marsphotos.ui.theme.MarsPhotosTheme 49 | 50 | @Composable 51 | fun HomeScreen( 52 | marsUiState: MarsUiState, 53 | retryAction: () -> Unit, 54 | modifier: Modifier = Modifier, 55 | contentPadding: PaddingValues = PaddingValues(0.dp), 56 | ) { 57 | when (marsUiState) { 58 | is MarsUiState.Loading -> LoadingScreen(modifier = modifier.fillMaxSize()) 59 | is MarsUiState.Success -> PhotosGridScreen( 60 | marsUiState.photos, contentPadding = contentPadding, modifier = modifier.fillMaxWidth() 61 | ) 62 | is MarsUiState.Error -> ErrorScreen(retryAction, modifier = modifier.fillMaxSize()) 63 | } 64 | } 65 | 66 | /** 67 | * The home screen displaying the loading message. 68 | */ 69 | @Composable 70 | fun LoadingScreen(modifier: Modifier = Modifier) { 71 | Image( 72 | modifier = modifier.size(200.dp), 73 | painter = painterResource(R.drawable.loading_img), 74 | contentDescription = stringResource(R.string.loading) 75 | ) 76 | } 77 | 78 | /** 79 | * The home screen displaying error message with re-attempt button. 80 | */ 81 | @Composable 82 | fun ErrorScreen(retryAction: () -> Unit, modifier: Modifier = Modifier) { 83 | Column( 84 | modifier = modifier, 85 | verticalArrangement = Arrangement.Center, 86 | horizontalAlignment = Alignment.CenterHorizontally 87 | ) { 88 | Image( 89 | painter = painterResource(id = R.drawable.ic_connection_error), contentDescription = "" 90 | ) 91 | Text(text = stringResource(R.string.loading_failed), modifier = Modifier.padding(16.dp)) 92 | Button(onClick = retryAction) { 93 | Text(stringResource(R.string.retry)) 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * The home screen displaying photo grid. 100 | */ 101 | @Composable 102 | fun PhotosGridScreen( 103 | photos: List, 104 | modifier: Modifier = Modifier, 105 | contentPadding: PaddingValues = PaddingValues(0.dp), 106 | ) { 107 | LazyVerticalGrid( 108 | columns = GridCells.Adaptive(150.dp), 109 | modifier = modifier.padding(horizontal = 4.dp), 110 | contentPadding = contentPadding, 111 | ) { 112 | items(items = photos, key = { photo -> photo.id }) { photo -> 113 | MarsPhotoCard( 114 | photo, 115 | modifier = Modifier 116 | .padding(4.dp) 117 | .fillMaxWidth() 118 | .aspectRatio(1.5f) 119 | ) 120 | } 121 | } 122 | } 123 | 124 | @Composable 125 | fun MarsPhotoCard(photo: MarsPhoto, modifier: Modifier = Modifier) { 126 | Card( 127 | modifier = modifier, 128 | shape = MaterialTheme.shapes.medium, 129 | elevation = CardDefaults.cardElevation(defaultElevation = 8.dp) 130 | ) { 131 | AsyncImage( 132 | model = ImageRequest.Builder(context = LocalContext.current).data(photo.imgSrc) 133 | .crossfade(true).build(), 134 | error = painterResource(R.drawable.ic_broken_image), 135 | placeholder = painterResource(R.drawable.loading_img), 136 | contentDescription = stringResource(R.string.mars_photo), 137 | contentScale = ContentScale.Crop, 138 | modifier = Modifier.fillMaxWidth() 139 | ) 140 | } 141 | } 142 | 143 | @Preview(showBackground = true) 144 | @Composable 145 | fun LoadingScreenPreview() { 146 | MarsPhotosTheme { 147 | LoadingScreen() 148 | } 149 | } 150 | 151 | @Preview(showBackground = true) 152 | @Composable 153 | fun ErrorScreenPreview() { 154 | MarsPhotosTheme { 155 | ErrorScreen({}) 156 | } 157 | } 158 | 159 | @Preview(showBackground = true) 160 | @Composable 161 | fun PhotosGridScreenPreview() { 162 | MarsPhotosTheme { 163 | val mockData = List(10) { MarsPhoto("$it", "") } 164 | PhotosGridScreen(mockData) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/marsphotos/ui/screens/MarsViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.marsphotos.ui.screens 17 | 18 | import androidx.compose.runtime.getValue 19 | import androidx.compose.runtime.mutableStateOf 20 | import androidx.compose.runtime.setValue 21 | import androidx.lifecycle.ViewModel 22 | import androidx.lifecycle.ViewModelProvider 23 | import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY 24 | import androidx.lifecycle.viewModelScope 25 | import androidx.lifecycle.viewmodel.initializer 26 | import androidx.lifecycle.viewmodel.viewModelFactory 27 | import com.example.marsphotos.MarsPhotosApplication 28 | import com.example.marsphotos.data.MarsPhotosRepository 29 | import com.example.marsphotos.model.MarsPhoto 30 | import kotlinx.coroutines.launch 31 | import retrofit2.HttpException 32 | import java.io.IOException 33 | 34 | /** 35 | * UI state for the Home screen 36 | */ 37 | sealed interface MarsUiState { 38 | data class Success(val photos: List) : MarsUiState 39 | object Error : MarsUiState 40 | object Loading : MarsUiState 41 | } 42 | 43 | class MarsViewModel(private val marsPhotosRepository: MarsPhotosRepository) : ViewModel() { 44 | /** The mutable State that stores the status of the most recent request */ 45 | var marsUiState: MarsUiState by mutableStateOf(MarsUiState.Loading) 46 | private set 47 | 48 | /** 49 | * Call getMarsPhotos() on init so we can display status immediately. 50 | */ 51 | init { 52 | getMarsPhotos() 53 | } 54 | 55 | /** 56 | * Gets Mars photos information from the Mars API Retrofit service and updates the 57 | * [MarsPhoto] [List] [MutableList]. 58 | */ 59 | fun getMarsPhotos() { 60 | viewModelScope.launch { 61 | marsUiState = MarsUiState.Loading 62 | marsUiState = try { 63 | MarsUiState.Success(marsPhotosRepository.getMarsPhotos()) 64 | } catch (e: IOException) { 65 | MarsUiState.Error 66 | } catch (e: HttpException) { 67 | MarsUiState.Error 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * Factory for [MarsViewModel] that takes [MarsPhotosRepository] as a dependency 74 | */ 75 | companion object { 76 | val Factory: ViewModelProvider.Factory = viewModelFactory { 77 | initializer { 78 | val application = (this[APPLICATION_KEY] as MarsPhotosApplication) 79 | val marsPhotosRepository = application.container.marsPhotosRepository 80 | MarsViewModel(marsPhotosRepository = marsPhotosRepository) 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/marsphotos/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.marsphotos.ui.theme 18 | 19 | import androidx.compose.ui.graphics.Color 20 | 21 | val Purple80 = Color(0xFFD0BCFF) 22 | val PurpleGrey80 = Color(0xFFCCC2DC) 23 | val Pink80 = Color(0xFFEFB8C8) 24 | 25 | val Purple40 = Color(0xFF6650a4) 26 | val PurpleGrey40 = Color(0xFF625b71) 27 | val Pink40 = Color(0xFF7D5260) 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/marsphotos/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.marsphotos.ui.theme 18 | 19 | import androidx.compose.foundation.shape.RoundedCornerShape 20 | import androidx.compose.material3.Shapes 21 | import androidx.compose.ui.unit.dp 22 | 23 | val Shapes = Shapes( 24 | small = RoundedCornerShape(4.dp), 25 | medium = RoundedCornerShape(16.dp), 26 | ) 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/marsphotos/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.marsphotos.ui.theme 18 | 19 | import android.app.Activity 20 | import android.os.Build 21 | import androidx.compose.foundation.isSystemInDarkTheme 22 | import androidx.compose.material3.MaterialTheme 23 | import androidx.compose.material3.darkColorScheme 24 | import androidx.compose.material3.dynamicDarkColorScheme 25 | import androidx.compose.material3.dynamicLightColorScheme 26 | import androidx.compose.material3.lightColorScheme 27 | import androidx.compose.runtime.Composable 28 | import androidx.compose.runtime.SideEffect 29 | import androidx.compose.ui.graphics.toArgb 30 | import androidx.compose.ui.platform.LocalContext 31 | import androidx.compose.ui.platform.LocalView 32 | import androidx.core.view.WindowCompat 33 | 34 | private val DarkColorScheme = darkColorScheme( 35 | primary = Purple80, 36 | secondary = PurpleGrey80, 37 | tertiary = Pink80 38 | ) 39 | 40 | private val LightColorScheme = lightColorScheme( 41 | primary = Purple40, 42 | secondary = PurpleGrey40, 43 | tertiary = Pink40 44 | ) 45 | 46 | @Composable 47 | fun MarsPhotosTheme( 48 | darkTheme: Boolean = isSystemInDarkTheme(), 49 | // Dynamic color is available on Android 12+ 50 | // Dynamic color in this app is turned off for learning purposes 51 | dynamicColor: Boolean = false, 52 | content: @Composable () -> Unit 53 | ) { 54 | val colorScheme = when { 55 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 56 | val context = LocalContext.current 57 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 58 | } 59 | 60 | darkTheme -> DarkColorScheme 61 | else -> LightColorScheme 62 | } 63 | val view = LocalView.current 64 | if (!view.isInEditMode) { 65 | SideEffect { 66 | val window = (view.context as Activity).window 67 | window.statusBarColor = colorScheme.primary.toArgb() 68 | WindowCompat 69 | .getInsetsController(window, view) 70 | .isAppearanceLightStatusBars = darkTheme 71 | } 72 | } 73 | MaterialTheme( 74 | colorScheme = colorScheme, 75 | typography = Typography, 76 | shapes = Shapes, 77 | content = content 78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/marsphotos/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.marsphotos.ui.theme 18 | 19 | import androidx.compose.material3.Typography 20 | import androidx.compose.ui.text.TextStyle 21 | import androidx.compose.ui.text.font.FontFamily 22 | import androidx.compose.ui.text.font.FontWeight 23 | import androidx.compose.ui.unit.sp 24 | 25 | // Set of Material typography styles to start with 26 | val Typography = Typography( 27 | bodyLarge = TextStyle( 28 | fontFamily = FontFamily.Default, 29 | fontWeight = FontWeight.Normal, 30 | fontSize = 16.sp, 31 | lineHeight = 24.sp, 32 | letterSpacing = 0.5.sp 33 | ) 34 | ) 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_broken_image.xml: -------------------------------------------------------------------------------- 1 | 16 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_connection_error.xml: -------------------------------------------------------------------------------- 1 | 16 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 16 | 21 | 26 | 29 | 32 | 35 | 38 | 41 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 61 | 62 | 63 | 64 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 16 | 21 | 26 | 29 | 32 | 35 | 38 | 41 | 44 | 47 | 50 | 53 | 56 | 59 | 62 | 65 | 70 | 75 | 80 | 85 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/loading_img.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 28 | 34 | 40 | 46 | 52 | 58 | 64 | 70 | 76 | 82 | 88 | 94 | 95 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos/8399c839ce5f4be66e0ae1103ed0e04121c97fe4/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos/8399c839ce5f4be66e0ae1103ed0e04121c97fe4/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos/8399c839ce5f4be66e0ae1103ed0e04121c97fe4/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos/8399c839ce5f4be66e0ae1103ed0e04121c97fe4/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos/8399c839ce5f4be66e0ae1103ed0e04121c97fe4/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos/8399c839ce5f4be66e0ae1103ed0e04121c97fe4/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos/8399c839ce5f4be66e0ae1103ed0e04121c97fe4/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos/8399c839ce5f4be66e0ae1103ed0e04121c97fe4/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos/8399c839ce5f4be66e0ae1103ed0e04121c97fe4/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos/8399c839ce5f4be66e0ae1103ed0e04121c97fe4/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | Mars Photos 18 | Failed to load 19 | Retry 20 | Loading 21 | Mars photo 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 |