├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── architecture │ │ └── clean │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── architecture │ │ │ └── clean │ │ │ ├── core │ │ │ ├── App.kt │ │ │ ├── Config.kt │ │ │ └── FragmentFactory.kt │ │ │ ├── data │ │ │ ├── mapper │ │ │ │ └── CloudErrorMapper.kt │ │ │ ├── repository │ │ │ │ └── AppRepoImp.kt │ │ │ ├── restful │ │ │ │ └── ApiService.kt │ │ │ └── source │ │ │ │ ├── cloud │ │ │ │ ├── BaseCloudRepository.kt │ │ │ │ └── CloudRepository.kt │ │ │ │ └── db │ │ │ │ ├── AppDatabase.kt │ │ │ │ └── FoodDao.kt │ │ │ ├── di │ │ │ ├── builder │ │ │ │ ├── ActivityBuilder.kt │ │ │ │ ├── AppViewModelBuilder.kt │ │ │ │ ├── MainActivityProviders.kt │ │ │ │ ├── RepositoryBuilder.kt │ │ │ │ └── ViewModelBuilder.kt │ │ │ ├── component │ │ │ │ └── CoreComponent.kt │ │ │ ├── module │ │ │ │ ├── ContextModule.kt │ │ │ │ ├── DataBaseModule.kt │ │ │ │ └── NetworkModule.kt │ │ │ └── qualifier │ │ │ │ └── ViewModelKey.kt │ │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── Food.kt │ │ │ │ ├── FoodDto.kt │ │ │ │ └── response │ │ │ │ │ ├── ErrorModel.kt │ │ │ │ │ └── ErrorStatus.kt │ │ │ ├── repository │ │ │ │ └── AppRepository.kt │ │ │ └── usecase │ │ │ │ ├── GetAllFoodsUseCase.kt │ │ │ │ ├── GetHomeUseCase.kt │ │ │ │ ├── InsertFoodsUseCase.kt │ │ │ │ └── base │ │ │ │ └── UseCase.kt │ │ │ └── ui │ │ │ ├── CustomBindingAdapter.kt │ │ │ ├── DataBindingViewHolder.kt │ │ │ ├── ViewModelFactory.kt │ │ │ └── home │ │ │ ├── HomeFragment.kt │ │ │ ├── HomeViewModel.kt │ │ │ ├── MainActivity.kt │ │ │ └── adapter │ │ │ └── HomeAdapter.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_account_circle_black_24dp.xml │ │ ├── ic_arrow_back_black_24dp.xml │ │ ├── ic_favorite_border_black_24dp.xml │ │ ├── ic_favorite_red_24dp.xml │ │ ├── ic_launcher_background.xml │ │ └── pic.png │ │ ├── font │ │ ├── font.xml │ │ ├── robboto.TTF │ │ ├── robboto_italic.TTF │ │ ├── roboto_light.ttf │ │ └── roboto_medium.ttf │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── food_item_row.xml │ │ ├── fragment_home.xml │ │ ├── nav_header_main.xml │ │ └── toolbar_main.xml │ │ ├── menu │ │ ├── activity_main_drawer.xml │ │ └── menu_detail.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 │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── network_security_configuration.xml │ └── test │ └── java │ └── com │ └── architecture │ └── clean │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/ 38 | 39 | # Keystore files 40 | # Uncomment the following line if you do not want to check your keystore files in. 41 | #*.jks 42 | 43 | # External native build folder generated in Android Studio 2.2 and later 44 | .externalNativeBuild 45 | 46 | # Google Services (e.g. APIs or Firebase) 47 | google-services.json 48 | 49 | # Freeline 50 | freeline.py 51 | freeline/ 52 | freeline_project_description.json 53 | 54 | # fastlane 55 | fastlane/report.xml 56 | fastlane/Preview.html 57 | fastlane/screenshots 58 | fastlane/test_output 59 | fastlane/readme.md 60 | 61 | *.DS_Store 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android-Clean-Architecture (Coroutine-MVVM-ROOM,RETROFIT) 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion 28 8 | defaultConfig { 9 | applicationId "com.cooking.coroutine" 10 | minSdkVersion 17 11 | targetSdkVersion 28 12 | versionCode 100 13 | versionName "1.0.0" 14 | multiDexEnabled true 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | dataBinding { 24 | enabled = true 25 | } 26 | compileOptions { 27 | targetCompatibility 1.8 28 | sourceCompatibility 1.8 29 | } 30 | 31 | } 32 | 33 | dependencies { 34 | implementation fileTree(dir: 'libs', include: ['*.jar']) 35 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 36 | implementation 'androidx.appcompat:appcompat:1.1.0-alpha01' 37 | implementation 'com.google.android.material:material:1.1.0-alpha02' 38 | implementation 'androidx.cardview:cardview:1.0.0' 39 | implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha01' 40 | implementation "com.squareup.picasso:picasso:$picasso_version" 41 | 42 | implementation 'androidx.multidex:multidex:2.0.1' 43 | 44 | implementation 'com.squareup.retrofit2:retrofit:2.4.0' 45 | implementation 'com.squareup.okhttp3:okhttp:3.11.0' 46 | implementation 'com.squareup.retrofit2:converter-gson:2.4.0' 47 | implementation 'com.google.code.gson:gson:2.8.2' 48 | implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1' 49 | implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' 50 | 51 | 52 | implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0-alpha01' 53 | kapt 'androidx.lifecycle:lifecycle-compiler:2.1.0-alpha01' 54 | implementation 'androidx.lifecycle:lifecycle-reactivestreams:2.1.0-alpha01' 55 | implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0-alpha01' 56 | 57 | 58 | implementation "com.google.dagger:dagger:$daggerVersion" 59 | implementation "com.google.dagger:dagger-android:$daggerVersion" 60 | implementation "com.google.dagger:dagger-android-support:$daggerVersion" 61 | kapt "com.google.dagger:dagger-compiler:$daggerVersion" 62 | kapt "com.google.dagger:dagger-android-processor:$daggerVersion" 63 | kapt "com.google.dagger:dagger-android-support:$daggerVersion" 64 | 65 | 66 | implementation 'androidx.room:room-runtime:2.1.0-alpha03' 67 | kapt 'androidx.room:room-compiler:2.1.0-alpha03' 68 | implementation'androidx.room:room-coroutines:2.1.0-alpha03' 69 | 70 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1' 71 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0' 72 | 73 | testImplementation 'junit:junit:4.12' 74 | androidTestImplementation 'androidx.test:runner:1.1.1' 75 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 76 | testImplementation 'org.mockito:mockito-inline:2.11.0' 77 | implementation 'androidx.arch.core:core-testing:2.0.0' 78 | implementation "com.nhaarman:mockito-kotlin-kt1.1:$mockitoKotlinVersion" 79 | 80 | } 81 | 82 | -------------------------------------------------------------------------------- /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/architecture/clean/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.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.getTargetContext() 22 | assertEquals("com.architecture.clean", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/core/App.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.core 2 | 3 | 4 | import android.content.Context 5 | import androidx.multidex.MultiDex 6 | import com.architecture.clean.di.component.DaggerCoreComponent 7 | import dagger.android.AndroidInjector 8 | import dagger.android.DaggerApplication 9 | 10 | class App : DaggerApplication() { 11 | override fun applicationInjector(): AndroidInjector { 12 | return DaggerCoreComponent 13 | .builder() 14 | .application(this) 15 | .build() 16 | } 17 | 18 | override fun onCreate() { 19 | super.onCreate() 20 | 21 | } 22 | override fun attachBaseContext(base: Context?) { 23 | super.attachBaseContext(base) 24 | MultiDex.install(this) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/core/Config.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.core 2 | 3 | object Config { 4 | var HOST = "http://www.recipepuppy.com" 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/core/FragmentFactory.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.core 2 | 3 | 4 | import com.architecture.clean.ui.home.HomeFragment 5 | 6 | object FragmentFactory{ 7 | 8 | fun getHomeFragment(supportFragmentManager: androidx.fragment.app.FragmentManager): HomeFragment { 9 | var fragment = supportFragmentManager.findFragmentByTag(HomeFragment.FRAGMENT_NAME) 10 | if (fragment == null) { 11 | fragment = HomeFragment() 12 | } 13 | return fragment as HomeFragment 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/data/mapper/CloudErrorMapper.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.data.mapper 2 | 3 | import android.util.Log 4 | import com.architecture.clean.domain.model.response.ErrorModel 5 | import com.architecture.clean.domain.model.response.ErrorStatus 6 | import com.google.gson.Gson 7 | import com.google.gson.JsonObject 8 | import okhttp3.ResponseBody 9 | import retrofit2.HttpException 10 | import java.io.IOException 11 | import java.net.SocketTimeoutException 12 | import java.net.UnknownHostException 13 | import javax.inject.Inject 14 | 15 | class CloudErrorMapper @Inject constructor(private val gson: Gson) { 16 | 17 | fun mapToDomainErrorException(throwable: Throwable?): ErrorModel { 18 | val errorModel: ErrorModel? = when (throwable) { 19 | 20 | // if throwable is an instance of HttpException 21 | // then attempt to parse error data from response body 22 | is HttpException -> { 23 | // handle UNAUTHORIZED situation (when token expired) 24 | if (throwable.code() == 401) { 25 | ErrorModel(ErrorStatus.UNAUTHORIZED) 26 | } else { 27 | getHttpError(throwable.response().errorBody()) 28 | } 29 | } 30 | 31 | // handle api call timeout error 32 | is SocketTimeoutException -> { 33 | ErrorModel("TIME OUT!!",0,ErrorStatus.TIMEOUT) 34 | } 35 | 36 | // handle connection error 37 | is IOException -> { 38 | ErrorModel("CHECK CONNECTION",0,ErrorStatus.NO_CONNECTION) 39 | } 40 | 41 | is UnknownHostException -> { 42 | ErrorModel("CHECK CONNECTION",0,ErrorStatus.NO_CONNECTION) 43 | } 44 | else -> null 45 | } 46 | return errorModel!! 47 | } 48 | 49 | /** 50 | * attempts to parse http response body and get error data from it 51 | * 52 | * @param body retrofit response body 53 | * @return returns an instance of [ErrorModel] with parsed data or NOT_DEFINED status 54 | */ 55 | private fun getHttpError(body: ResponseBody?): ErrorModel { 56 | return try { 57 | // use response body to get error detail 58 | val result = body?.string() 59 | Log.d("getHttpError", "getErrorMessage() called with: errorBody = [$result]") 60 | val json = Gson().fromJson(result, JsonObject::class.java) 61 | ErrorModel( json.toString(), 400, ErrorStatus.BAD_RESPONSE) 62 | } catch (e: Throwable) { 63 | e.printStackTrace() 64 | ErrorModel(ErrorStatus.NOT_DEFINED) 65 | } 66 | 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/data/repository/AppRepoImp.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.data.repository 2 | 3 | import com.architecture.clean.data.source.cloud.BaseCloudRepository 4 | import com.architecture.clean.data.source.db.FoodDao 5 | import com.architecture.clean.domain.model.Food 6 | import com.architecture.clean.domain.model.FoodDto 7 | import com.architecture.clean.domain.repository.AppRepository 8 | import javax.inject.Inject 9 | 10 | class AppRepoImp @Inject constructor( 11 | private val cloudRepository: BaseCloudRepository, 12 | private val foodDao: FoodDao 13 | ) : AppRepository { 14 | override suspend fun selectAllFoods(): MutableList { 15 | 16 | return foodDao.selectAllFoods() 17 | 18 | } 19 | 20 | override suspend fun saveFoods(foodDto: FoodDto): Long { 21 | if (foodDto.results.size > 0) { 22 | for (food in foodDto.results) { 23 | foodDao.insertFood(food) 24 | } 25 | 26 | } 27 | return 0L 28 | } 29 | 30 | override suspend fun getHome(): FoodDto { 31 | return cloudRepository 32 | .getHome() 33 | } 34 | 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/data/restful/ApiService.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.data.restful 2 | 3 | import com.architecture.clean.domain.model.FoodDto 4 | import kotlinx.coroutines.Deferred 5 | import retrofit2.http.GET 6 | 7 | interface ApiService { 8 | 9 | @GET("api/") 10 | fun getHome( 11 | ): Deferred 12 | 13 | 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/data/source/cloud/BaseCloudRepository.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.data.source.cloud 2 | 3 | import com.architecture.clean.domain.model.FoodDto 4 | 5 | interface BaseCloudRepository { 6 | suspend fun getHome(): FoodDto 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/data/source/cloud/CloudRepository.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.data.source.cloud 2 | 3 | import com.architecture.clean.data.restful.ApiService 4 | import com.architecture.clean.domain.model.FoodDto 5 | 6 | class CloudRepository(private val apIs: ApiService) : BaseCloudRepository { 7 | override suspend fun getHome(): FoodDto { 8 | return apIs.getHome().await() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/data/source/db/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.data.source.db 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.architecture.clean.domain.model.Food 6 | 7 | @Database(entities = [Food::class], version = AppDatabase.VERSION) 8 | abstract class AppDatabase : RoomDatabase() { 9 | companion object { 10 | const val DB_NAME = "foods.db" 11 | const val VERSION = 1 12 | } 13 | abstract fun foodDao(): FoodDao 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/data/source/db/FoodDao.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.data.source.db 2 | 3 | import androidx.room.* 4 | import com.architecture.clean.domain.model.Food 5 | 6 | 7 | @Dao 8 | interface FoodDao { 9 | 10 | @Insert(onConflict = OnConflictStrategy.REPLACE) 11 | suspend fun insertFood(food: Food): Long 12 | 13 | @Delete 14 | suspend fun deleteFood(food: Food): Int 15 | 16 | @Query("SELECT * from Food") 17 | suspend fun selectAllFoods(): MutableList 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/di/builder/ActivityBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.di.builder 2 | 3 | import com.architecture.clean.ui.home.MainActivity 4 | import dagger.Module 5 | import dagger.android.ContributesAndroidInjector 6 | 7 | @Module 8 | abstract class ActivityBuilder { 9 | 10 | @ContributesAndroidInjector(modules = [MainActivityProviders::class]) 11 | abstract fun bindMainActivity(): MainActivity 12 | 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/di/builder/AppViewModelBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.di.builder 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.architecture.clean.di.qualifier.ViewModelKey 5 | import com.architecture.clean.ui.home.HomeViewModel 6 | import dagger.Binds 7 | import dagger.Module 8 | import dagger.multibindings.IntoMap 9 | 10 | @Module 11 | abstract class AppViewModelBuilder { 12 | @Binds 13 | @IntoMap 14 | @ViewModelKey(HomeViewModel::class) 15 | abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): ViewModel 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/di/builder/MainActivityProviders.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.di.builder 2 | 3 | import com.architecture.clean.ui.home.HomeFragment 4 | import dagger.Module 5 | import dagger.android.ContributesAndroidInjector 6 | 7 | @Module 8 | abstract class MainActivityProviders{ 9 | @ContributesAndroidInjector 10 | abstract fun provideHomeFragment(): HomeFragment 11 | 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/di/builder/RepositoryBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.di.builder 2 | 3 | import com.architecture.clean.data.repository.AppRepoImp 4 | import com.architecture.clean.domain.repository.AppRepository 5 | import dagger.Binds 6 | import dagger.Module 7 | 8 | @Module 9 | abstract class RepositoryBuilder { 10 | @Binds 11 | abstract fun bindsMovieRepository(repoImp: AppRepoImp): AppRepository 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/di/builder/ViewModelBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.di.builder 2 | 3 | import androidx.lifecycle.ViewModelProvider 4 | import com.architecture.clean.ui.ViewModelFactory 5 | import dagger.Binds 6 | import dagger.Module 7 | 8 | @Module(includes = [ 9 | (RepositoryBuilder::class), 10 | (AppViewModelBuilder::class) 11 | ]) 12 | abstract class ViewModelBuilder { 13 | 14 | @Binds 15 | abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/di/component/CoreComponent.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.di.component 2 | 3 | import android.app.Application 4 | import com.architecture.clean.core.App 5 | import com.architecture.clean.di.builder.ActivityBuilder 6 | import com.architecture.clean.di.module.ContextModule 7 | import com.architecture.clean.di.module.DataBaseModule 8 | import com.architecture.clean.di.module.NetworkModule 9 | import dagger.BindsInstance 10 | import dagger.Component 11 | import dagger.android.AndroidInjector 12 | import javax.inject.Singleton 13 | import dagger.android.support.AndroidSupportInjectionModule 14 | 15 | @Singleton 16 | @Component(modules = [AndroidSupportInjectionModule::class, NetworkModule::class, ActivityBuilder::class, ContextModule::class,DataBaseModule::class]) 17 | interface CoreComponent : AndroidInjector { 18 | 19 | @Component.Builder 20 | interface Builder { 21 | @BindsInstance 22 | fun application(application: Application): Builder 23 | 24 | fun build(): CoreComponent 25 | } 26 | 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/di/module/ContextModule.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.di.module 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import com.architecture.clean.di.builder.ViewModelBuilder 6 | import dagger.Module 7 | import dagger.Provides 8 | import javax.inject.Singleton 9 | 10 | @Module(includes = [ViewModelBuilder::class]) 11 | class ContextModule { 12 | 13 | @Provides 14 | @Singleton 15 | fun provideContext(application: Application): Context { 16 | return application 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/di/module/DataBaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.di.module 2 | 3 | import android.app.Application 4 | import androidx.room.Room 5 | import com.architecture.clean.data.source.db.AppDatabase 6 | import com.architecture.clean.data.source.db.FoodDao 7 | import dagger.Module 8 | import dagger.Provides 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | class DataBaseModule { 13 | 14 | @Provides 15 | @Singleton 16 | fun provideRoomDatabase(application: Application): AppDatabase { 17 | return Room 18 | .databaseBuilder(application, AppDatabase::class.java, AppDatabase.DB_NAME) 19 | .fallbackToDestructiveMigration() 20 | .build() 21 | } 22 | 23 | @Provides 24 | fun provideUserDao(appDataBase: AppDatabase): FoodDao { 25 | return appDataBase.foodDao() 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/di/module/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.di.module 2 | 3 | import com.architecture.clean.core.Config 4 | import com.architecture.clean.data.restful.ApiService 5 | import com.architecture.clean.data.source.cloud.BaseCloudRepository 6 | import com.architecture.clean.data.source.cloud.CloudRepository 7 | import com.google.gson.Gson 8 | import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory 9 | import dagger.Module 10 | import dagger.Provides 11 | import okhttp3.OkHttpClient 12 | import okhttp3.logging.HttpLoggingInterceptor 13 | import retrofit2.Retrofit 14 | import retrofit2.converter.gson.GsonConverterFactory 15 | import java.util.concurrent.TimeUnit 16 | import javax.inject.Singleton 17 | 18 | @Module 19 | class NetworkModule { 20 | 21 | @Provides 22 | @Singleton 23 | fun providesRetrofit( 24 | gsonConverterFactory: GsonConverterFactory, 25 | okHttpClient: OkHttpClient 26 | ): Retrofit { 27 | return Retrofit.Builder().baseUrl(Config.HOST) 28 | .addConverterFactory(gsonConverterFactory) 29 | .addCallAdapterFactory(CoroutineCallAdapterFactory()) 30 | .client(okHttpClient) 31 | .build() 32 | } 33 | 34 | @Provides 35 | @Singleton 36 | fun providesOkHttpClient(): OkHttpClient { 37 | val client = OkHttpClient.Builder() 38 | // .cache(cache) 39 | .connectTimeout(60, TimeUnit.SECONDS) 40 | .writeTimeout(60, TimeUnit.SECONDS) 41 | .readTimeout(60, TimeUnit.SECONDS) 42 | val interceptor = HttpLoggingInterceptor() 43 | interceptor.level = HttpLoggingInterceptor.Level.BODY 44 | client.addNetworkInterceptor(interceptor) 45 | return client.build() 46 | } 47 | 48 | 49 | @Provides 50 | @Singleton 51 | fun providesGson(): Gson { 52 | return Gson() 53 | } 54 | 55 | @Provides 56 | @Singleton 57 | fun providesGsonConverterFactory(): GsonConverterFactory { 58 | return GsonConverterFactory.create() 59 | } 60 | 61 | 62 | 63 | @Singleton 64 | @Provides 65 | fun provideService( retrofit: Retrofit): ApiService { 66 | return retrofit.create(ApiService::class.java) 67 | } 68 | 69 | 70 | @Provides 71 | fun provideCloudRepository(apIs: ApiService): BaseCloudRepository { 72 | return CloudRepository(apIs) 73 | } 74 | 75 | 76 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/di/qualifier/ViewModelKey.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.di.qualifier 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.MapKey 5 | import kotlin.reflect.KClass 6 | 7 | @MustBeDocumented 8 | @Target( 9 | AnnotationTarget.FUNCTION, 10 | AnnotationTarget.PROPERTY_GETTER, 11 | AnnotationTarget.PROPERTY_SETTER 12 | ) 13 | @Retention(AnnotationRetention.RUNTIME) 14 | @MapKey 15 | annotation class ViewModelKey(val value: KClass) -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/domain/model/Food.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.domain.model 2 | 3 | 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import android.os.Parcel 7 | import android.os.Parcelable 8 | import com.google.gson.annotations.SerializedName 9 | 10 | 11 | @Entity 12 | data class Food( 13 | @PrimaryKey(autoGenerate = true) 14 | @SerializedName("id") var id: Int, 15 | @SerializedName("title") var title: String, 16 | @SerializedName("href") var href: String, 17 | @SerializedName("ingredients") var ingredients: String, 18 | @SerializedName("thumbnail") var thumbnail: String 19 | ) : Parcelable { 20 | constructor(source: Parcel) : this( 21 | source.readInt(), 22 | source.readString(), 23 | source.readString(), 24 | source.readString(), 25 | source.readString() 26 | ) 27 | 28 | override fun describeContents() = 0 29 | 30 | override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) { 31 | writeInt(id) 32 | writeString(title) 33 | writeString(href) 34 | writeString(ingredients) 35 | writeString(thumbnail) 36 | } 37 | 38 | companion object { 39 | @JvmField 40 | val CREATOR: Parcelable.Creator = object : Parcelable.Creator { 41 | override fun createFromParcel(source: Parcel): Food = Food(source) 42 | override fun newArray(size: Int): Array = arrayOfNulls(size) 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/domain/model/FoodDto.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.domain.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class FoodDto( 6 | @SerializedName("results") val results: ArrayList 7 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/domain/model/response/ErrorModel.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.domain.model.response 2 | 3 | /** 4 | * Default error model that comes from server if something goes wrong with a repository call 5 | */ 6 | data class ErrorModel( 7 | val message: String?, 8 | val code: Int?, 9 | @Transient var errorStatus: ErrorStatus 10 | ) { 11 | constructor(errorStatus: ErrorStatus) : this(null, null, errorStatus) 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/domain/model/response/ErrorStatus.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.domain.model.response 2 | 3 | /** 4 | * various error status to know what happened if something goes wrong with a repository call 5 | */ 6 | enum class ErrorStatus { 7 | /** 8 | * error in connecting to repository (Server or Database) 9 | */ 10 | NO_CONNECTION, 11 | /** 12 | * error in getting value (Json Error, Server Error, etc) 13 | */ 14 | BAD_RESPONSE, 15 | /** 16 | * Time out error 17 | */ 18 | TIMEOUT, 19 | /** 20 | * no data available in repository 21 | */ 22 | EMPTY_RESPONSE, 23 | /** 24 | * an unexpected error 25 | */ 26 | NOT_DEFINED, 27 | /** 28 | * bad credential 29 | */ 30 | UNAUTHORIZED 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/domain/repository/AppRepository.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.domain.repository 2 | 3 | import com.architecture.clean.domain.model.Food 4 | import com.architecture.clean.domain.model.FoodDto 5 | 6 | interface AppRepository{ 7 | suspend fun getHome(): FoodDto 8 | suspend fun saveFoods(foodDto: FoodDto): Long 9 | suspend fun selectAllFoods() : MutableList 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/domain/usecase/GetAllFoodsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.domain.usecase 2 | 3 | import com.architecture.clean.data.mapper.CloudErrorMapper 4 | import com.architecture.clean.domain.model.Food 5 | import com.architecture.clean.domain.repository.AppRepository 6 | import com.architecture.clean.domain.usecase.base.UseCase 7 | import javax.inject.Inject 8 | 9 | class GetAllFoodsUseCase @Inject constructor( 10 | errorUtil: CloudErrorMapper, 11 | private val appRepository: AppRepository 12 | ) : UseCase>(errorUtil) { 13 | override suspend fun executeOnBackground(): MutableList { 14 | return appRepository.selectAllFoods() 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/domain/usecase/GetHomeUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.domain.usecase 2 | 3 | import com.architecture.clean.data.mapper.CloudErrorMapper 4 | import com.architecture.clean.domain.model.FoodDto 5 | import com.architecture.clean.domain.repository.AppRepository 6 | import com.architecture.clean.domain.usecase.base.UseCase 7 | import javax.inject.Inject 8 | 9 | class GetHomeUseCase @Inject constructor( 10 | errorUtil: CloudErrorMapper, 11 | private val appRepository: AppRepository 12 | ) : UseCase(errorUtil) { 13 | override suspend fun executeOnBackground():FoodDto { 14 | return appRepository.getHome() 15 | } 16 | 17 | 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/domain/usecase/InsertFoodsUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.domain.usecase 2 | 3 | import com.architecture.clean.data.mapper.CloudErrorMapper 4 | import com.architecture.clean.domain.model.FoodDto 5 | import com.architecture.clean.domain.repository.AppRepository 6 | import com.architecture.clean.domain.usecase.base.UseCase 7 | import javax.inject.Inject 8 | 9 | class InsertFoodsUseCase @Inject constructor( 10 | errorUtil: CloudErrorMapper, 11 | private val appRepository: AppRepository 12 | ) : UseCase(errorUtil) { 13 | var foodDto=FoodDto(arrayListOf()) 14 | override suspend fun executeOnBackground(): Long { 15 | return appRepository.saveFoods(foodDto) 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/domain/usecase/base/UseCase.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.domain.usecase.base 2 | 3 | 4 | import com.architecture.clean.data.mapper.CloudErrorMapper 5 | import com.architecture.clean.domain.model.response.ErrorModel 6 | import kotlinx.coroutines.* 7 | import kotlin.coroutines.CoroutineContext 8 | 9 | typealias CompletionBlock = UseCase.Request.() -> Unit 10 | 11 | abstract class UseCase(val errorUtil: CloudErrorMapper) { 12 | 13 | private var parentJob: Job = Job() 14 | var backgroundContext: CoroutineContext = Dispatchers.IO 15 | var foregroundContext: CoroutineContext = Dispatchers.Main 16 | 17 | protected abstract suspend fun executeOnBackground(): T 18 | 19 | fun execute(block: CompletionBlock) { 20 | val response = Request().apply { block() } 21 | unsubscribe() 22 | parentJob = Job() 23 | CoroutineScope(foregroundContext + parentJob).launch { 24 | try { 25 | val result = withContext(backgroundContext) { 26 | executeOnBackground() 27 | } 28 | response(result) 29 | } catch (cancellationException: CancellationException) { 30 | response(cancellationException) 31 | } catch (e: Exception) { 32 | val error = errorUtil.mapToDomainErrorException(e) 33 | response(error) 34 | } 35 | } 36 | } 37 | 38 | 39 | fun unsubscribe() { 40 | parentJob.apply { 41 | cancelChildren() 42 | cancel() 43 | } 44 | } 45 | 46 | 47 | 48 | class Request { 49 | private var onComplete: ((T) -> Unit)? = null 50 | private var onError: ((ErrorModel) -> Unit)? = null 51 | private var onCancel: ((CancellationException) -> Unit)? = null 52 | 53 | fun onComplete(block: (T) -> Unit) { 54 | onComplete = block 55 | } 56 | 57 | fun onError(block: (ErrorModel) -> Unit) { 58 | 59 | onError = block 60 | 61 | } 62 | 63 | fun onCancel(block: (CancellationException) -> Unit) { 64 | onCancel = block 65 | } 66 | 67 | 68 | operator fun invoke(result: T) { 69 | onComplete?.let { 70 | it.invoke(result) 71 | } 72 | } 73 | 74 | operator fun invoke(error: ErrorModel) { 75 | onError?.let { 76 | it.invoke(error) 77 | 78 | } 79 | } 80 | 81 | operator fun invoke(error: CancellationException) { 82 | onCancel?.let { 83 | it.invoke(error) 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/ui/CustomBindingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.ui 2 | 3 | import androidx.databinding.BindingAdapter 4 | import android.widget.ImageView 5 | import com.squareup.picasso.Picasso 6 | 7 | object CustomBindingAdapter{ 8 | 9 | @JvmStatic 10 | @BindingAdapter("bind:image_url") 11 | fun loadImage(imageView: ImageView, url: String) { 12 | Picasso.with(imageView.context).load(url).into(imageView) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/ui/DataBindingViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.ui 2 | 3 | import androidx.databinding.DataBindingUtil 4 | import androidx.databinding.ViewDataBinding 5 | import androidx.annotation.IdRes 6 | import androidx.annotation.NonNull 7 | import android.view.LayoutInflater 8 | import android.view.ViewGroup 9 | 10 | 11 | abstract class DataBindingViewHolder 12 | 13 | ( val dataBinding: ViewDataBinding) 14 | 15 | : androidx.recyclerview.widget.RecyclerView.ViewHolder(dataBinding.root) { 16 | 17 | constructor(@NonNull inflater: LayoutInflater, @IdRes layoutId: Int, 18 | @NonNull parent: ViewGroup, @NonNull attachToParent: Boolean) : 19 | this(DataBindingUtil.inflate(inflater, layoutId, parent, attachToParent)) 20 | 21 | 22 | abstract fun onBind(t: T) 23 | } 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/ui/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.ui 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import javax.inject.Inject 6 | import javax.inject.Provider 7 | import javax.inject.Singleton 8 | 9 | @Singleton 10 | class ViewModelFactory 11 | @Inject constructor(private val creators: Map, @JvmSuppressWildcards Provider>) : ViewModelProvider.Factory { 12 | 13 | override fun create(modelClass: Class): T { 14 | // get the ViewModel provider based on given class 15 | val creator = creators[modelClass] ?: creators.entries.firstOrNull { 16 | modelClass.isAssignableFrom(it.key) 17 | }?.value ?: throw IllegalArgumentException("Unknown model class $modelClass") 18 | try { 19 | @Suppress("UNCHECKED_CAST") 20 | return creator.get() as T 21 | } catch (e: Exception) { 22 | throw RuntimeException(e) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/ui/home/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.ui.home 2 | 3 | import androidx.lifecycle.Observer 4 | import androidx.lifecycle.ViewModelProvider 5 | import androidx.lifecycle.ViewModelProviders 6 | import android.os.Bundle 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.Toast 11 | import androidx.recyclerview.widget.RecyclerView 12 | import com.architecture.clean.R 13 | import com.architecture.clean.domain.model.FoodDto 14 | import com.architecture.clean.ui.home.adapter.HomeAdapter 15 | import dagger.android.support.DaggerFragment 16 | import kotlinx.android.synthetic.main.fragment_home.* 17 | import javax.inject.Inject 18 | import android.util.Pair as UtilPair 19 | 20 | 21 | class HomeFragment: DaggerFragment() { 22 | private val TAG: String = HomeFragment::class.java.simpleName 23 | companion object { 24 | val FRAGMENT_NAME: String = HomeFragment::class.java.name 25 | } 26 | @Inject 27 | lateinit var viewModelFactory: ViewModelProvider.Factory 28 | private val viewModel: HomeViewModel by lazy { ViewModelProviders.of(this,viewModelFactory).get(HomeViewModel::class.java) } 29 | val adapter : HomeAdapter by lazy { HomeAdapter(arrayListOf()) } 30 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 31 | return inflater.inflate(R.layout.fragment_home, container, false) 32 | } 33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 34 | super.onViewCreated(view, savedInstanceState) 35 | with(viewModel) { 36 | homeData.observe(this@HomeFragment, Observer { 37 | initView(it) 38 | } 39 | ) 40 | error.observe(this@HomeFragment, Observer { 41 | progressBar_home.visibility= View.GONE 42 | Toast.makeText(context, "${it?.message}", Toast.LENGTH_LONG).show() 43 | }) 44 | 45 | foodsCount.observe(this@HomeFragment, Observer { 46 | Toast.makeText(context,"you have $it foods in your dataBase !!",Toast.LENGTH_LONG).show() 47 | }) 48 | } 49 | } 50 | 51 | private fun initView(it: FoodDto?) { 52 | rv_main_home.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context, RecyclerView.VERTICAL, false) 53 | rv_main_home.adapter = adapter 54 | progressBar_home.visibility=View.GONE 55 | if (it!!.results.isNotEmpty()) { 56 | adapter.clear() 57 | adapter.add(it.results) 58 | 59 | }else{ 60 | Toast.makeText(context, context?.getString(R.string.empty_list), android.widget.Toast.LENGTH_LONG).show() 61 | } 62 | } 63 | 64 | 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/ui/home/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.ui.home 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import android.util.Log 5 | import androidx.lifecycle.ViewModel 6 | import com.architecture.clean.domain.model.FoodDto 7 | import com.architecture.clean.domain.model.response.ErrorModel 8 | import com.architecture.clean.domain.model.response.ErrorStatus 9 | import com.architecture.clean.domain.usecase.GetAllFoodsUseCase 10 | import com.architecture.clean.domain.usecase.GetHomeUseCase 11 | import com.architecture.clean.domain.usecase.InsertFoodsUseCase 12 | import javax.inject.Inject 13 | 14 | class HomeViewModel @Inject constructor(private val getHomeUseCase: GetHomeUseCase, 15 | private val insertFoodsUseCase: InsertFoodsUseCase, 16 | private val getAllFoodsUseCase: GetAllFoodsUseCase 17 | ) : ViewModel() { 18 | private val TAG = HomeViewModel::class.java.simpleName 19 | val homeData: MutableLiveData by lazy { MutableLiveData() } 20 | val error : MutableLiveData by lazy { MutableLiveData() } 21 | val foodsCount : MutableLiveData by lazy { MutableLiveData() } 22 | init { 23 | 24 | getHomeUseCase.execute{ 25 | onComplete { 26 | Log.d(TAG, it.toString()) 27 | homeData.value = it 28 | insert(it) 29 | } 30 | 31 | onError { throwable -> 32 | if(throwable.errorStatus== ErrorStatus.UNAUTHORIZED){ 33 | doReshresh() 34 | }else{ 35 | error.value=throwable 36 | } 37 | 38 | } 39 | 40 | onCancel { 41 | 42 | } 43 | } 44 | } 45 | 46 | private fun doReshresh() { 47 | 48 | } 49 | 50 | 51 | fun insert(foodDto: FoodDto){ 52 | insertFoodsUseCase.foodDto=foodDto 53 | insertFoodsUseCase.execute { 54 | 55 | onComplete { 56 | returnFoodsInDb() 57 | } 58 | 59 | onError { throwable -> 60 | error.value=throwable 61 | } 62 | 63 | onCancel { 64 | 65 | } 66 | } 67 | } 68 | 69 | private fun returnFoodsInDb() { 70 | getAllFoodsUseCase.execute { 71 | onComplete { 72 | foodsCount.value=it.size 73 | } 74 | onError { 75 | error.value=it 76 | } 77 | } 78 | } 79 | 80 | override fun onCleared() { 81 | super.onCleared() 82 | insertFoodsUseCase.unsubscribe() 83 | getHomeUseCase.unsubscribe() 84 | getAllFoodsUseCase.unsubscribe() 85 | } 86 | } -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/ui/home/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.ui.home 2 | 3 | 4 | import android.os.Bundle 5 | import androidx.appcompat.widget.Toolbar 6 | import com.architecture.clean.R 7 | import com.architecture.clean.core.FragmentFactory 8 | import dagger.android.support.DaggerAppCompatActivity 9 | import kotlinx.android.synthetic.main.toolbar_main.* 10 | 11 | class MainActivity : DaggerAppCompatActivity() { 12 | private val TAG = MainActivity::class.java.simpleName 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | setContentView(R.layout.activity_main) 17 | val toolbar: Toolbar by lazy { toolbar_main_activity } 18 | setSupportActionBar(toolbar) 19 | supportActionBar!!.setDisplayShowTitleEnabled(false) 20 | showHomeFragment() 21 | } 22 | 23 | private fun showHomeFragment() { 24 | val fragmentTransaction = supportFragmentManager.beginTransaction() 25 | .replace(R.id.container, 26 | FragmentFactory.getHomeFragment(supportFragmentManager), 27 | HomeFragment.FRAGMENT_NAME) 28 | fragmentTransaction.addToBackStack(HomeFragment.FRAGMENT_NAME) 29 | fragmentTransaction.commit() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/architecture/clean/ui/home/adapter/HomeAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.architecture.clean.ui.home.adapter 2 | 3 | import androidx.databinding.ViewDataBinding 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import com.architecture.clean.BR.item 7 | import com.architecture.clean.databinding.FoodItemRowBinding 8 | import com.architecture.clean.domain.model.Food 9 | import com.architecture.clean.ui.DataBindingViewHolder 10 | 11 | class HomeAdapter( 12 | private var items: ArrayList = arrayListOf() 13 | ) : androidx.recyclerview.widget.RecyclerView.Adapter() { 14 | override fun getItemCount(): Int = items.size 15 | 16 | override fun onBindViewHolder(holder: SimpleHolder, position: Int) { 17 | holder.onBind(items[position]) 18 | } 19 | 20 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SimpleHolder { 21 | val binding = FoodItemRowBinding.inflate(LayoutInflater.from(parent.context),parent,false) 22 | return SimpleHolder(binding) 23 | } 24 | 25 | inner class SimpleHolder(dataBinding: ViewDataBinding) 26 | : DataBindingViewHolder(dataBinding) { 27 | override fun onBind(t: Food): Unit = with(t) { 28 | dataBinding.setVariable(item,t) 29 | } 30 | } 31 | 32 | fun add(list: ArrayList) { 33 | items.addAll(list) 34 | notifyDataSetChanged() 35 | } 36 | 37 | fun clear() { 38 | items.clear() 39 | notifyDataSetChanged() 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_account_circle_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite_border_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite_red_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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/pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/drawable/pic.png -------------------------------------------------------------------------------- /app/src/main/res/font/font.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 23 | 24 | 25 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/font/robboto.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/font/robboto.TTF -------------------------------------------------------------------------------- /app/src/main/res/font/robboto_italic.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/font/robboto_italic.TTF -------------------------------------------------------------------------------- /app/src/main/res/font/roboto_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/font/roboto_light.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/roboto_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/font/roboto_medium.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/food_item_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 20 | 23 | 28 | 35 | 45 | 57 | 69 | 70 | 77 | 78 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 15 | 16 | 25 | 26 | 30 | 31 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/nav_header_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/toolbar_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_main_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_detail.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /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/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samira-badamestani/android-clean-arc-coroutines/c19acbc887811aa2e48109f4d88db753a18c0a0a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ccdd79 4 | #CAD966 5 | #73830a 6 | #fff 7 | #FAF8F8 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 180dp 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Food 3 | Please wait... 4 | Health 5 | Sign in 6 | Start 7 | Alicia Scott 8 | Today 03:14PM 9 | oops, List is empty. 10 | DetailActivity 11 | 12 | "Material is the metaphor.\n\n" 13 | 14 | "A material metaphor is the unifying theory of a rationalized space and a system of motion." 15 | "The material is grounded in tactile reality, inspired by the study of paper and ink, yet " 16 | "technologically advanced and open to imagination and magic.\n" 17 | "Surfaces and edges of the material provide visual cues that are grounded in reality. The " 18 | "use of familiar tactile attributes helps users quickly understand affordances. Yet the " 19 | "flexibility of the material creates new affordances that supercede those in the physical " 20 | "world, without breaking the rules of physics.\n" 21 | "The fundamentals of light, surface, and movement are key to conveying how objects move, " 22 | "interact, and exist in space and in relation to each other. Realistic lighting shows " 23 | "seams, divides space, and indicates moving parts.\n\n" 24 | 25 | Settings 26 | not_clicked 27 | clicked 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 |