├── .gitignore ├── .idea ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── markdown-navigator-enh.xml ├── markdown-navigator.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── android │ │ └── myapplication │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── android │ │ │ └── myapplication │ │ │ ├── Constants.kt │ │ │ ├── TodoApplication.kt │ │ │ ├── data │ │ │ ├── AppDatabase.kt │ │ │ ├── Banner.kt │ │ │ ├── Converters.kt │ │ │ ├── HomeRepository.kt │ │ │ ├── Plant.kt │ │ │ ├── PlantDao.kt │ │ │ └── Response.kt │ │ │ ├── network │ │ │ ├── MainNetwork.kt │ │ │ └── SkipNetworkInterceptor.kt │ │ │ ├── ui │ │ │ ├── MainActivity.kt │ │ │ ├── dashboard │ │ │ │ └── DashboardFragment.kt │ │ │ ├── detail │ │ │ │ ├── DetailActivity.kt │ │ │ │ └── DetailFragment.kt │ │ │ ├── home │ │ │ │ └── HomeFragment.kt │ │ │ └── notifications │ │ │ │ └── NotificationsFragment.kt │ │ │ ├── util │ │ │ └── InjectorUtils.kt │ │ │ ├── viewmodel │ │ │ ├── HomeViewModel.kt │ │ │ └── HomeViewModelFactory.kt │ │ │ └── workers │ │ │ └── RefreshDataWork.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_dashboard_black_24dp.xml │ │ ├── ic_detail_back.xml │ │ ├── ic_home_black_24dp.xml │ │ ├── ic_launcher_background.xml │ │ └── ic_notifications_black_24dp.xml │ │ ├── layout │ │ ├── activity_detail.xml │ │ ├── activity_main.xml │ │ ├── fragment_dashboard.xml │ │ ├── fragment_detail.xml │ │ ├── fragment_home.xml │ │ └── fragment_notifications.xml │ │ ├── menu │ │ └── bottom_nav_menu.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 │ │ ├── navigation │ │ └── mobile_navigation.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── network_security_config.xml │ └── test │ └── java │ └── com │ └── android │ └── myapplication │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | My Application -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/markdown-navigator-enh.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Jetpack 最佳开发姿势 2 | 在Android架构组件基础上,融入Kotlin 协程+retrofit,模拟网络,全面快速开发。 3 | ## Navigation 4 | NavController在 NavHost 中管理应用导航的对象,沿导航图中的特定路径导航至特定目标,或直接导航至特定目标。 5 | 6 | 首先,定义 layout/activity_main.xml 7 | ``` 8 | 19 | ``` 20 | 其次,定义 navigation/mobile_navigation.xml 21 | Activity的添加,需要先在Project创建对应的Activity,即可在布局设计器处理。 22 | 23 | ``` 24 | 30 | 31 | 34 | 35 | ... 36 | 40 | 43 | 44 | 45 | 46 | ``` 47 | 最后,页面跳转,参数传递 48 | ``` 49 | val direction = HomeFragmentDirections.actionNavigationHomeToDetailActivity(plantId) 50 | view.findNavController().navigate(direction) 51 | ``` 52 | 参数接收: 53 | ``` 54 | private val args: DetailActivityArgs by navArgs() 55 | ``` 56 | 57 | 58 | ## Databinding 59 | 在onCreateView()中直接使用控件,会报空指针异常,这个姿势 binding.tvNavigation 是可以的。 60 | 61 | ``` 62 | val binding = FragmentHomeBinding.inflate(inflater, container, false) 63 | binding.tvNavigation.setOnClickListener { 64 | navigateToDetailPage("1", it) 65 | } 66 | 67 | ``` 68 | 布局文件中,字符串拼接,如跟ViewModel一起使用: 69 | 70 | ``` 71 | android:text='@{"Data From Network-> "+viewModel.response}' 72 | ``` 73 | 74 | 75 | 76 | 77 | ## ViewModel 78 | 以生命周期的方式存储和管理界面相关的数据。 79 | Kotlin协程 viewModelScope, 如果 ViewModel 已清除,则在此范围内启动的协程都会自动取消。 80 | 81 | ``` 82 | private val homeViewModel: HomeViewModel by viewModels { 83 | InjectorUtils.provideHomeViewModelFactory(requireContext()) 84 | } 85 | 86 | viewModelScope.launch { 87 | ... 88 | } 89 | ``` 90 | 91 | 92 | ## LiveData 93 | 一种可观察的数据存储器类,具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。 94 | 95 | ``` 96 | var plantName = gardenPlantings.map { 97 | ... 98 | } 99 | ``` 100 | map 实现LiveData的转换 101 | 102 | 103 | ## Room 104 | 创建应用数据的缓存, SQLite 的基础上提供了一个抽象层,充分利用 SQLite 的强大功能,更强健的数据库访问机制。 105 | 106 | 使用 Room 引用复杂数据,Room 提供了在基本类型和包装类型之间进行转换的功能,但不允许实体之间进行对象引用。 107 | 108 | 要为自定义类型添加此类支持,您需要提供一个 TypeConverter,它可以在自定义类与 Room 可以保留的已知类型之间来回转换。 109 | 110 | ``` 111 | class Converters {//TypeConverters 112 | ... 113 | } 114 | ``` 115 | 将 @TypeConverters 注释添加到 AppDatabase 类中,以便 Room 可以使用您为该 AppDatabase 中的每个实体和 DAO 定义的转换器: 116 | 117 | ``` 118 | @Database(entities = table, version = 1, exportSchema = false) 119 | @TypeConverters(Converters::class) 120 | abstract class AppDatabase : RoomDatabase() { 121 | ... 122 | } 123 | 124 | ``` 125 | 126 | ``` 127 | @Insert 128 | suspend fun insertPlant(plant: Plant): Long 129 | ``` 130 | 认为是协程suspend 131 | 132 | 133 | 134 | ## WorkManager 135 | 136 | 使用 WorkManager API 可以轻松地调度即使在应用退出或设备重启时仍应运行的可延迟异步任务。 137 | 138 | ``` 139 | val workManagerConfiguration = Configuration.Builder() 140 | .setWorkerFactory(RefreshDataWork.Factory()) 141 | .build() 142 | 143 | WorkManager.initialize(appContext, workManagerConfiguration) 144 | val constraints = Constraints.Builder() 145 | .setRequiresCharging(true) 146 | .setRequiredNetworkType(NetworkType.CONNECTED) 147 | .build() 148 | 149 | val work = PeriodicWorkRequestBuilder(2, TimeUnit.HOURS) 150 | .setConstraints(constraints) 151 | .build() 152 | 153 | WorkManager.getInstance(appContext) 154 | .enqueueUniquePeriodicWork(RefreshDataWork::class.java.name, KEEP, work) 155 | 156 | ``` 157 | 158 | PeriodicWorkRequest 用于重复或重复工作,最小间隔应为15分钟。 159 | 160 | OneTimeWorkRequest 一次性申请,不重复工作。 161 | 162 | WorkManager按顺序执行,单例模式,app启动时执行一次。 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | apply plugin: 'kotlin-kapt' 8 | 9 | apply plugin: 'androidx.navigation.safeargs.kotlin' 10 | 11 | 12 | android { 13 | compileSdkVersion 30 14 | buildToolsVersion "29.0.3" 15 | dataBinding { 16 | enabled = true 17 | } 18 | defaultConfig { 19 | applicationId "com.android.myapplication" 20 | minSdkVersion 21 21 | targetSdkVersion 30 22 | versionCode 1 23 | versionName "1.0" 24 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 25 | } 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | // work-runtime-ktx 2.1.0 and above now requires Java 8 38 | kotlinOptions { 39 | jvmTarget = "1.8" 40 | } 41 | } 42 | 43 | dependencies { 44 | implementation fileTree(dir: 'libs', include: ['*.jar']) 45 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 46 | implementation 'androidx.appcompat:appcompat:1.2.0' 47 | implementation 'androidx.core:core-ktx:1.3.1' 48 | implementation 'com.google.android.material:material:1.3.0-alpha02' 49 | implementation 'androidx.constraintlayout:constraintlayout:2.0.1' 50 | 51 | implementation 'androidx.navigation:navigation-fragment:2.3.0' 52 | implementation 'androidx.navigation:navigation-ui:2.3.0' 53 | implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0' 54 | implementation 'androidx.navigation:navigation-ui-ktx:2.3.0' 55 | 56 | kapt "androidx.room:room-compiler:2.2.5" 57 | implementation "androidx.room:room-runtime:2.2.5" 58 | implementation "androidx.room:room-ktx:2.2.5" 59 | 60 | implementation "androidx.work:work-runtime-ktx:2.4.0" 61 | 62 | // network & serialization 63 | implementation "com.google.code.gson:gson:2.8.6" 64 | implementation "com.squareup.retrofit2:converter-gson:2.7.1" 65 | implementation "com.squareup.retrofit2:retrofit:2.7.1" 66 | 67 | 68 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' 69 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" 70 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" 71 | 72 | implementation "androidx.work:work-runtime-ktx:2.4.0" 73 | 74 | implementation "com.jakewharton.timber:timber:4.7.1" 75 | 76 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 77 | 78 | testImplementation 'junit:junit:4.13' 79 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 80 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 81 | // androidTestImplementation "androidx.work:work-testing:2.3.2" 82 | } 83 | -------------------------------------------------------------------------------- /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 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/android/myapplication/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.android.myapplication", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication 2 | 3 | /** 4 | * Constants used throughout the app. 5 | */ 6 | const val DATABASE_NAME = "test.db" -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/TodoApplication.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.work.* 6 | import androidx.work.ExistingPeriodicWorkPolicy.KEEP 7 | import com.android.myapplication.workers.RefreshDataWork 8 | import timber.log.Timber 9 | import timber.log.Timber.DebugTree 10 | import java.util.concurrent.TimeUnit 11 | import kotlin.properties.Delegates 12 | 13 | open class TodoApplication : Application() { 14 | companion object { 15 | var appContext: Context by Delegates.notNull() 16 | 17 | } 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | if (BuildConfig.DEBUG) Timber.plant(DebugTree()) 22 | appContext = applicationContext 23 | workManagerJob() 24 | } 25 | 26 | 27 | private fun workManagerJob() { 28 | val workManagerConfiguration = Configuration.Builder() 29 | .setWorkerFactory(RefreshDataWork.Factory()) 30 | .build() 31 | 32 | WorkManager.initialize(appContext, workManagerConfiguration) 33 | val constraints = Constraints.Builder() 34 | .setRequiresCharging(true) 35 | .setRequiredNetworkType(NetworkType.CONNECTED) 36 | .build() 37 | // Specify that the work should attempt to run every day 38 | val work = PeriodicWorkRequestBuilder(2, TimeUnit.HOURS) 39 | .setConstraints(constraints) 40 | .build() 41 | 42 | // Enqueue it work WorkManager, keeping any previously scheduled jobs for the same 43 | // work. 44 | WorkManager.getInstance(appContext) 45 | .enqueueUniquePeriodicWork(RefreshDataWork::class.java.name, KEEP, work) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/data/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.data 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import androidx.room.TypeConverters 8 | import androidx.sqlite.db.SupportSQLiteDatabase 9 | import com.android.myapplication.DATABASE_NAME 10 | import timber.log.Timber 11 | 12 | /** 13 | * The Room database for this app 14 | */ 15 | @Database(entities = [Plant::class], version = 1, exportSchema = false) 16 | @TypeConverters(Converters::class) 17 | abstract class AppDatabase : RoomDatabase() { 18 | abstract fun gardenPlantingDao(): PlantDao 19 | 20 | companion object { 21 | 22 | // For Singleton instantiation 23 | @Volatile 24 | private var instance: AppDatabase? = null 25 | 26 | fun getInstance(context: Context): AppDatabase { 27 | Timber.e("AppDatabase", "getInstance") 28 | return instance ?: synchronized(this) { 29 | instance ?: buildDatabase(context).also { instance = it } 30 | } 31 | } 32 | 33 | // Create and pre-populate the database. See this article for more details: 34 | // https://medium.com/google-developers/7-pro-tips-for-room-fbadea4bfbd1#4785 35 | private fun buildDatabase(context: Context): AppDatabase { 36 | return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) 37 | .addCallback(object : RoomDatabase.Callback() { 38 | override fun onCreate(db: SupportSQLiteDatabase) { 39 | super.onCreate(db) 40 | } 41 | }) 42 | .fallbackToDestructiveMigration() 43 | .build() 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/data/Banner.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.data 2 | 3 | // "desc":"一起来做个App吧", 4 | //      "id":10, 5 | //      "imagePath":"http://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png", 6 | //      "isVisible":1, 7 | //      "order":0, 8 | //      "title":"一起来做个App吧", 9 | //      "type":0, 10 | //      "url":"http://www.wanandroid.com/blog/show/2" 11 | data class Banner(val desc: String, 12 | val id: Int, 13 | val imagePath: String, 14 | val isVisible: Int, 15 | val order: Int, 16 | val title: String, 17 | val type: Int, 18 | val url: String) -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/data/Converters.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 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.android.myapplication.data 18 | 19 | import androidx.room.TypeConverter 20 | import java.util.Calendar 21 | 22 | /** 23 | * Type converters to allow Room to reference complex data types. 24 | */ 25 | class Converters {//TypeConverters 26 | @TypeConverter fun calendarToDatestamp(calendar: Calendar): Long = calendar.timeInMillis 27 | 28 | @TypeConverter fun datestampToCalendar(value: Long): Calendar = 29 | Calendar.getInstance().apply { timeInMillis = value } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/data/HomeRepository.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.data 2 | import com.android.myapplication.network.MainNetwork 3 | import kotlinx.coroutines.withTimeout 4 | import timber.log.Timber 5 | 6 | class HomeRepository private constructor( 7 | val network: MainNetwork, 8 | private val gardenPlantingDao: PlantDao 9 | ) { 10 | suspend fun createGardenPlanting(plantName: String) { 11 | val gardenPlanting = Plant(plantName) 12 | gardenPlantingDao.insertPlant(gardenPlanting) 13 | } 14 | 15 | suspend fun removeGardenPlanting(gardenPlanting: Plant) { 16 | gardenPlantingDao.deletePlant(gardenPlanting) 17 | } 18 | 19 | 20 | fun getGardenPlantings() = gardenPlantingDao.getGardenPlantings() 21 | 22 | 23 | suspend fun refreshView(): String { 24 | try { 25 | val result = withTimeout(50_00) { 26 | network.fetchNextTitle() 27 | } 28 | createGardenPlanting(result) 29 | return result 30 | } catch (error: Throwable) { 31 | throw RefreshError("Unable to refresh title", error) 32 | } 33 | } 34 | 35 | suspend fun getBanner(): Response>{ 36 | try { 37 | 38 | return withTimeout(50_00) { 39 | Timber.e("respo getBanner") 40 | network.getBanner() 41 | } 42 | } catch (error: Throwable) { 43 | Timber.e( "err-- "+error.message) 44 | throw RefreshError("Unable to refresh title ->", error) 45 | } 46 | } 47 | 48 | 49 | class RefreshError(message: String, cause: Throwable) : Throwable(message, cause) 50 | 51 | 52 | companion object { 53 | 54 | // For Singleton instantiation 55 | @Volatile 56 | private var instance: HomeRepository? = null 57 | 58 | fun getInstance(network: MainNetwork, gardenPlantingDao: PlantDao) = 59 | instance ?: synchronized(this) { 60 | instance ?: HomeRepository(network, gardenPlantingDao).also { instance = it } 61 | } 62 | } 63 | 64 | 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/data/Plant.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.data 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import java.util.Calendar 7 | 8 | /** 9 | * [Plant] represents when a user adds a [Plant] to their garden, with useful metadata. 10 | * Properties such as [lastWateringDate] are used for notifications (such as when to water the 11 | * plant). 12 | * 13 | * Declaring the column info allows for the renaming of variables without implementing a 14 | * database migration, as the column name would not change. 15 | */ 16 | @Entity 17 | data class Plant( 18 | @ColumnInfo(name = "plant_name") val plantName: String, 19 | 20 | /** 21 | * Indicates when the [Plant] was planted. Used for showing notification when it's time 22 | * to harvest the plant. 23 | */ 24 | @ColumnInfo(name = "plant_date") val homeDate: Calendar = Calendar.getInstance() 25 | 26 | ) { 27 | @PrimaryKey(autoGenerate = true) 28 | @ColumnInfo(name = "id") 29 | var id: Long = 0 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/data/PlantDao.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.data 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Delete 6 | import androidx.room.Insert 7 | import androidx.room.Query 8 | 9 | /** 10 | * The Data Access Object for the [Plant] class. 11 | */ 12 | @Dao 13 | interface PlantDao { 14 | @Query("SELECT * FROM plant") 15 | fun getGardenPlantings(): LiveData> 16 | 17 | @Insert 18 | suspend fun insertPlant(plant: Plant): Long 19 | 20 | @Delete 21 | suspend fun deletePlant(plant: Plant) 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/data/Response.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.data 2 | data class Response(val errorCode: Int, val errorMsg: String, val data: T) -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/network/MainNetwork.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.network 2 | 3 | import androidx.lifecycle.LiveData 4 | import com.android.myapplication.data.Banner 5 | import com.android.myapplication.data.Response 6 | import okhttp3.OkHttpClient 7 | import retrofit2.Retrofit 8 | import retrofit2.converter.gson.GsonConverterFactory 9 | import retrofit2.http.GET 10 | 11 | val okHttpClient = OkHttpClient.Builder() 12 | .addInterceptor(NetworkInterceptor()) 13 | .build() 14 | 15 | val retrofit = Retrofit.Builder() 16 | .baseUrl("https://www.wanandroid.com") 17 | .client(okHttpClient) 18 | .addConverterFactory(GsonConverterFactory.create()) 19 | .build() 20 | 21 | interface MainNetwork { 22 | @GET("next_title.json") 23 | suspend fun fetchNextTitle(): String 24 | 25 | @GET("/banner/json") 26 | suspend fun getBanner(): Response> 27 | } 28 | 29 | object Api { 30 | val retrofitService: MainNetwork by lazy { retrofit.create(MainNetwork::class.java) } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/network/SkipNetworkInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.network 2 | 3 | import com.google.gson.Gson 4 | import okhttp3.* 5 | import timber.log.Timber 6 | 7 | /** 8 | * A list of fake results to return. 9 | */ 10 | private val FAKE_RESULTS = listOf( 11 | "Apple", 12 | "Beet", 13 | "Grape", 14 | "Orange", 15 | "Pear" 16 | ) 17 | 18 | /** 19 | * This class will return fake [Response] objects to Retrofit, without actually using the network. 20 | */ 21 | class NetworkInterceptor() : Interceptor { 22 | private var lastResult: String = "" 23 | val gson = Gson() 24 | 25 | private var attempts = 0 26 | 27 | /** 28 | * Return true iff this request should error. 29 | */ 30 | private fun wantRandomError() = attempts++ % 5 == 0 31 | 32 | /** 33 | * Stop the request from actually going out to the network. 34 | */ 35 | override fun intercept(chain: Interceptor.Chain): Response { 36 | val wantRandomError = wantRandomError() 37 | return if (wantRandomError) { 38 | makeErrorResult(chain.request()) 39 | } else { 40 | makeOkResult(chain, chain.request()) 41 | } 42 | } 43 | 44 | 45 | /** 46 | * Generate an error result. 47 | * 48 | * ``` 49 | * HTTP/1.1 500 Bad server day 50 | * Content-type: application/json 51 | * 52 | * {"cause": "not sure"} 53 | * ``` 54 | */ 55 | private fun makeErrorResult(request: Request): Response { 56 | return Response.Builder() 57 | .code(500) 58 | .request(request) 59 | .protocol(Protocol.HTTP_1_1) 60 | .message("Bad server day") 61 | .body( 62 | ResponseBody.create( 63 | MediaType.get("application/json"), 64 | gson.toJson(mapOf("cause" to "not sure")) 65 | ) 66 | ) 67 | .build() 68 | } 69 | 70 | /** 71 | * Generate a success response. 72 | * 73 | * ``` 74 | * HTTP/1.1 200 OK 75 | * Content-type: application/json 76 | * 77 | * "$random_string" 78 | * ``` 79 | */ 80 | private fun makeOkResult(chain: Interceptor.Chain, request: Request): Response { 81 | val url = chain.request().url().toString() 82 | if (url.contains("banner")) 83 | return chain.proceed(request) 84 | var nextResult = lastResult 85 | while (nextResult == lastResult) { 86 | nextResult = FAKE_RESULTS.random() 87 | } 88 | lastResult = nextResult 89 | 90 | return Response.Builder() 91 | .code(200) 92 | .request(request) 93 | .protocol(Protocol.HTTP_1_1) 94 | .message("OK") 95 | .body( 96 | ResponseBody.create( 97 | MediaType.get("application/json"), 98 | gson.toJson(nextResult) 99 | ) 100 | ) 101 | .build() 102 | } 103 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.ui 2 | 3 | import android.os.Bundle 4 | import com.google.android.material.bottomnavigation.BottomNavigationView 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.navigation.findNavController 7 | import androidx.navigation.ui.setupWithNavController 8 | import com.android.myapplication.R 9 | 10 | class MainActivity : AppCompatActivity() { 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.activity_main) 15 | val navView: BottomNavigationView = findViewById(R.id.nav_view) 16 | val navController = findNavController(R.id.nav_host_fragment) 17 | navView.setupWithNavController(navController) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/ui/dashboard/DashboardFragment.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.ui.dashboard 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import com.android.myapplication.databinding.FragmentDashboardBinding 9 | 10 | class DashboardFragment : Fragment() { 11 | 12 | override fun onCreateView( 13 | inflater: LayoutInflater, 14 | container: ViewGroup?, 15 | savedInstanceState: Bundle? 16 | ): View? { 17 | val binding = FragmentDashboardBinding.inflate(inflater, container, false) 18 | return binding.root 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/ui/detail/DetailActivity.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.ui.detail 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.navigation.navArgs 6 | import com.android.myapplication.R 7 | import androidx.databinding.DataBindingUtil.setContentView 8 | import com.android.myapplication.databinding.ActivityDetailBinding 9 | import kotlinx.android.synthetic.main.activity_detail.* 10 | import timber.log.Timber 11 | 12 | /** 13 | * Created by Albert on 2020/3/6. 14 | * Description: 15 | */ 16 | class DetailActivity : AppCompatActivity() { 17 | private val args: DetailActivityArgs by navArgs() 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | setContentView(this,R.layout.activity_detail) 22 | val detailId = args.detailId 23 | Timber.e("detailId", detailId) 24 | toolbar.setNavigationOnClickListener { 25 | finish() 26 | } 27 | 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/ui/detail/DetailFragment.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.ui.detail 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import com.android.myapplication.databinding.FragmentDetailBinding 9 | 10 | /** 11 | * Created by Albert on 2020/3/2. 12 | * Description: 13 | */ 14 | class DetailFragment : Fragment() { 15 | 16 | override fun onCreateView( 17 | inflater: LayoutInflater, 18 | container: ViewGroup?, 19 | savedInstanceState: Bundle? 20 | ): View? { 21 | val binding = FragmentDetailBinding.inflate(inflater, container, false) 22 | return binding.root 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/ui/home/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.ui.home 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.Toast 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.fragment.app.Fragment 10 | import androidx.fragment.app.viewModels 11 | import androidx.lifecycle.Observer 12 | import androidx.navigation.findNavController 13 | import com.android.myapplication.databinding.FragmentHomeBinding 14 | import com.android.myapplication.util.InjectorUtils 15 | import com.android.myapplication.viewmodel.HomeViewModel 16 | import kotlinx.android.synthetic.main.fragment_home.* 17 | import timber.log.Timber 18 | 19 | class HomeFragment : Fragment() { 20 | private val TAG: String = HomeFragment::class.java.name 21 | private val homeViewModel: HomeViewModel by viewModels { 22 | InjectorUtils.provideHomeViewModelFactory(requireContext()) 23 | } 24 | 25 | override fun onCreateView( 26 | inflater: LayoutInflater, 27 | container: ViewGroup?, 28 | savedInstanceState: Bundle? 29 | ): View? { 30 | val binding = FragmentHomeBinding.inflate(inflater, container, false) 31 | binding.viewModel = homeViewModel 32 | binding.lifecycleOwner = viewLifecycleOwner 33 | 34 | binding.tvNavigation.setOnClickListener { 35 | navigateToDetailPage("1", it) 36 | } 37 | 38 | binding.tvHttp.setOnClickListener { 39 | homeViewModel.getBanner() 40 | } 41 | 42 | binding.rootLayout.setOnClickListener { 43 | homeViewModel.onHomeViewClicked() 44 | } 45 | 46 | 47 | homeViewModel.banners.observe(viewLifecycleOwner, Observer { 48 | Toast.makeText(this.context, it[0].title, Toast.LENGTH_LONG).show() 49 | 50 | //Timber.e("result-> "+banner.title) 51 | }) 52 | 53 | homeViewModel.err.observe(viewLifecycleOwner, Observer { 54 | Toast.makeText(activity, "error : " + it, Toast.LENGTH_LONG).show() 55 | }) 56 | (activity as AppCompatActivity).setSupportActionBar(binding.toolbar) 57 | return binding.root 58 | } 59 | 60 | 61 | private fun navigateToDetailPage(plantId: String, view: View) { 62 | val direction = 63 | HomeFragmentDirections.actionNavigationHomeToDetailActivity(plantId) 64 | view.findNavController().navigate(direction) 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/ui/notifications/NotificationsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.ui.notifications 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import com.android.myapplication.databinding.FragmentNotificationsBinding 9 | 10 | class NotificationsFragment : Fragment() { 11 | 12 | override fun onCreateView( 13 | inflater: LayoutInflater, 14 | container: ViewGroup?, 15 | savedInstanceState: Bundle? 16 | ): View? { 17 | val binding = FragmentNotificationsBinding.inflate(inflater, container, false) 18 | 19 | return binding.root 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/util/InjectorUtils.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.util 2 | 3 | import android.content.Context 4 | import com.android.myapplication.data.AppDatabase 5 | import com.android.myapplication.data.HomeRepository 6 | import com.android.myapplication.network.Api 7 | import com.android.myapplication.viewmodel.HomeViewModelFactory 8 | import timber.log.Timber 9 | 10 | /** 11 | * Static methods used to inject classes needed for various Activities and Fragments. 12 | */ 13 | object InjectorUtils { 14 | 15 | private val TAG: String=InjectorUtils::class.java.simpleName 16 | 17 | fun getHomeRepository(context: Context): HomeRepository { 18 | Timber.e(TAG,"getHomeRepository") 19 | return HomeRepository.getInstance( 20 | Api.retrofitService, 21 | AppDatabase.getInstance(context.applicationContext).gardenPlantingDao()) 22 | } 23 | 24 | fun provideHomeViewModelFactory( 25 | context: Context 26 | ): HomeViewModelFactory { 27 | Timber.e(TAG,"provideHomeViewModelFactory") 28 | val repository = getHomeRepository(context) 29 | return HomeViewModelFactory(repository) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/viewmodel/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.viewmodel 2 | 3 | import android.widget.Toast 4 | import androidx.lifecycle.* 5 | import com.android.myapplication.data.Banner 6 | import com.android.myapplication.data.Plant 7 | import com.android.myapplication.data.HomeRepository 8 | import com.android.myapplication.data.Response 9 | import kotlinx.coroutines.launch 10 | import timber.log.Timber 11 | 12 | class HomeViewModel internal constructor(private val homeRepository: HomeRepository) : ViewModel() { 13 | 14 | // The internal MutableLiveData String that stores the most recent response 15 | private val _response = MutableLiveData() 16 | 17 | private val _banners =MutableLiveData>() 18 | 19 | // The external immutable LiveData for the response String 20 | val response: LiveData get() = _response 21 | 22 | val banners: LiveData> get() = _banners 23 | 24 | // The internal MutableLiveData String that stores the most recent response 25 | private val _err = MutableLiveData() 26 | 27 | // The external immutable LiveData for the response String 28 | val err: LiveData get() = _err 29 | 30 | 31 | val gardenPlantings: LiveData> = 32 | homeRepository.getGardenPlantings() 33 | 34 | var plantName = gardenPlantings.map { 35 | if (0 < it.size) 36 | it.takeLast(1)[0].plantName 37 | else null 38 | 39 | } 40 | 41 | fun onHomeViewClicked() { 42 | refreshView() 43 | } 44 | 45 | 46 | /* private val result:LiveData=launchDataLoad { 47 | val result:LiveData= homeRepository.refreshView() 48 | }*/ 49 | private fun refreshView() = launchDataLoad { 50 | val result: String = homeRepository.refreshView() 51 | _response.value = result 52 | } 53 | 54 | 55 | private fun launchDataLoad(block: suspend () -> Unit): Unit { 56 | viewModelScope.launch { 57 | try { 58 | // _spinner.value = true 59 | block() 60 | } catch (error: HomeRepository.RefreshError) { 61 | //Timber.e("vm ", error.message) 62 | _err.value = error.message 63 | } finally { 64 | // _spinner.value = false 65 | } 66 | } 67 | 68 | 69 | } 70 | 71 | fun getBanner() = launchDataLoad { 72 | val result: Response> = homeRepository.getBanner() 73 | _banners.postValue(result.data) 74 | 75 | } 76 | 77 | 78 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/viewmodel/HomeViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import com.android.myapplication.data.HomeRepository 6 | import timber.log.Timber 7 | 8 | /** 9 | * Factory for creating a [GardenPlantingListViewModel] with a constructor that takes a 10 | * [GardenPlantingRepository]. 11 | */ 12 | class HomeViewModelFactory( 13 | private val repository: HomeRepository 14 | ) : ViewModelProvider.NewInstanceFactory() { 15 | 16 | @Suppress("UNCHECKED_CAST") 17 | override fun create(modelClass: Class): T { 18 | //Timber.e("HomeViewModelFactory","gen ViewModel:"+modelClass.simpleName) 19 | return HomeViewModel(repository) as T 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/myapplication/workers/RefreshDataWork.kt: -------------------------------------------------------------------------------- 1 | package com.android.myapplication.workers 2 | 3 | import android.content.Context 4 | import androidx.work.CoroutineWorker 5 | import androidx.work.ListenableWorker 6 | import androidx.work.WorkerFactory 7 | import androidx.work.WorkerParameters 8 | import com.android.myapplication.util.InjectorUtils 9 | 10 | /** 11 | * Worker job to refresh titles from the network while the app is in the background. 12 | * 13 | * WorkManager is a library used to enqueue work that is guaranteed to execute after its constraints 14 | * are met. It can run work even when the app is in the background, or not running. 15 | */ 16 | class RefreshDataWork(context: Context, params: WorkerParameters) : 17 | CoroutineWorker(context, params) { 18 | 19 | override suspend fun doWork(): Result { 20 | return try { 21 | 22 | var repository = InjectorUtils.getHomeRepository(applicationContext) 23 | repository.refreshView() 24 | Result.success() 25 | } catch (error: Error) { 26 | Result.failure() 27 | } 28 | } 29 | 30 | class Factory() : WorkerFactory() { 31 | override fun createWorker( 32 | appContext: Context, 33 | workerClassName: String, 34 | workerParameters: WorkerParameters 35 | ): ListenableWorker? { 36 | 37 | return RefreshDataWork(appContext, workerParameters) 38 | } 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dashboard_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_detail_back.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 23 | 24 | 28 | 29 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 11 | 16 | 17 | 23 | 24 | 32 | 33 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 50 | 51 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 22 | 23 | 29 | 30 | 35 | 36 | 43 | 44 | 45 | 46 | 47 | 53 | 54 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 22 | 23 | 29 | 30 | 35 | 36 | 43 | 44 | 45 | 46 | 47 | 53 | 54 | 68 | 69 | 81 | 82 | 94 | 95 | 96 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_notifications.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 22 | 23 | 29 | 30 | 35 | 36 | 43 | 44 | 45 | 46 | 47 | 53 | 54 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/res/menu/bottom_nav_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbertShen0211/Android-architecture-components/fcd5cef3825376c5769a636f74089f7bdbd72cfc/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbertShen0211/Android-architecture-components/fcd5cef3825376c5769a636f74089f7bdbd72cfc/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbertShen0211/Android-architecture-components/fcd5cef3825376c5769a636f74089f7bdbd72cfc/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbertShen0211/Android-architecture-components/fcd5cef3825376c5769a636f74089f7bdbd72cfc/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbertShen0211/Android-architecture-components/fcd5cef3825376c5769a636f74089f7bdbd72cfc/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbertShen0211/Android-architecture-components/fcd5cef3825376c5769a636f74089f7bdbd72cfc/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbertShen0211/Android-architecture-components/fcd5cef3825376c5769a636f74089f7bdbd72cfc/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbertShen0211/Android-architecture-components/fcd5cef3825376c5769a636f74089f7bdbd72cfc/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbertShen0211/Android-architecture-components/fcd5cef3825376c5769a636f74089f7bdbd72cfc/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlbertShen0211/Android-architecture-components/fcd5cef3825376c5769a636f74089f7bdbd72cfc/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/navigation/mobile_navigation.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 17 | 18 | 23 | 24 | 29 | 30 | 34 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | My Application 3 | Home 4 | Detail 5 | Dashboard 6 | Notifications 7 | 8 | 9 | Hello blank fragment 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 15 | 16 |