├── .gitignore ├── .idea ├── compiler.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── schemas │ └── de.dabotz.shoppinglist.database.AppDatabase │ │ ├── 1.json │ │ └── 2.json └── src │ ├── androidTest │ └── java │ │ └── de │ │ └── dabotz │ │ └── shoppinglist │ │ ├── ExampleInstrumentedTest.kt │ │ ├── GrocerListItemEntityTest.kt │ │ └── MigrationTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── de │ │ │ └── dabotz │ │ │ └── shoppinglist │ │ │ ├── Extensions.kt │ │ │ ├── MainActivity.kt │ │ │ ├── ShoppingListApplication.kt │ │ │ ├── database │ │ │ ├── AppDatabase.kt │ │ │ ├── DateConverter.kt │ │ │ ├── GroceryListItemDAO.kt │ │ │ └── Migrations.kt │ │ │ ├── models │ │ │ ├── GroceryListItem.kt │ │ │ ├── GroceryListItemViewModel.kt │ │ │ └── SelectedId.kt │ │ │ └── modules │ │ │ ├── detail │ │ │ ├── DetailActivity.kt │ │ │ ├── DetailModule.kt │ │ │ └── ShoppingItemFragment.kt │ │ │ └── list │ │ │ ├── GroceryListItemAdapter.kt │ │ │ ├── ListModule.kt │ │ │ └── ShoppingListFragment.kt │ └── res │ │ ├── drawable │ │ └── ic_check_black_24dp.xml │ │ ├── layout │ │ ├── detail_layout.xml │ │ ├── dual_pane.xml │ │ ├── f_shopping_item.xml │ │ ├── f_shopping_list.xml │ │ ├── single_pane.xml │ │ └── vh_grocerylist.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-xlarge-land │ │ └── layouts.xml │ │ ├── values-xlarge-port │ │ └── layouts.xml │ │ └── values │ │ ├── colors.xml │ │ ├── font_certs.xml │ │ ├── layouts.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── de │ └── dabotz │ └── shoppinglist │ └── 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/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 23 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Architecture Components Example 2 | A Sample Project for the new Android Architecture Components Libs with: 3 | 4 | - Lifcycle Handling 5 | - Room (a new ORM for SQLite) 6 | - Unittests 7 | - Dagger2.11+ (Dagger Branch) 8 | - Kodein(Kodein Branch) 9 | 10 | built in Kotlin -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-android-extensions' 5 | apply plugin: 'kotlin-kapt' 6 | 7 | android { 8 | dataBinding { 9 | enabled = true 10 | } 11 | 12 | compileSdkVersion 27 13 | buildToolsVersion "26.0.2" 14 | defaultConfig { 15 | applicationId "de.dabotz.shoppinglist" 16 | minSdkVersion 17 17 | targetSdkVersion 27 18 | versionCode 1 19 | versionName "1.0" 20 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 21 | javaCompileOptions { 22 | annotationProcessorOptions { 23 | arguments = ["room.schemaLocation": 24 | "$projectDir/schemas".toString()] 25 | } 26 | } 27 | } 28 | 29 | buildTypes { 30 | release { 31 | minifyEnabled false 32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 33 | } 34 | } 35 | 36 | sourceSets { 37 | androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation fileTree(dir: 'libs', include: ['*.jar']) 43 | 44 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 45 | implementation 'com.android.support:appcompat-v7:27.0.2' 46 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 47 | implementation 'com.android.support:design:27.0.2' 48 | 49 | 50 | //Lifecycles, Livedata, ViewModel 51 | implementation "android.arch.lifecycle:extensions:1.0.0" 52 | kapt "android.arch.lifecycle:compiler:1.0.0" 53 | 54 | //Room 55 | implementation "android.arch.persistence.room:runtime:1.0.0" 56 | kapt "android.arch.persistence.room:compiler:1.0.0" 57 | 58 | kapt 'com.android.databinding:compiler:2.3.1' 59 | 60 | implementation 'com.github.salomonbrys.kodein:kodein:4.1.0' 61 | implementation 'com.github.salomonbrys.kodein:kodein-android:4.1.0' 62 | 63 | //Test 64 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { 65 | exclude group: 'com.android.support', module: 'support-annotations' 66 | }) 67 | testImplementation 'junit:junit:4.12' 68 | androidTestImplementation "android.arch.persistence.room:testing:1.0.0" 69 | androidTestImplementation 'com.android.support:support-annotations:27.0.2' 70 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 71 | androidTestImplementation 'com.android.support.test:rules:1.0.1' 72 | androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' 73 | } 74 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Botz/Library/androidSDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/schemas/de.dabotz.shoppinglist.database.AppDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "1f6c69ff95d18933eb88e034999c59c1", 6 | "entities": [ 7 | { 8 | "tableName": "GroceryListItem", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `count` INTEGER, `created` INTEGER)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER" 15 | }, 16 | { 17 | "fieldPath": "name", 18 | "columnName": "name", 19 | "affinity": "TEXT" 20 | }, 21 | { 22 | "fieldPath": "count", 23 | "columnName": "count", 24 | "affinity": "INTEGER" 25 | }, 26 | { 27 | "fieldPath": "created", 28 | "columnName": "created", 29 | "affinity": "INTEGER" 30 | } 31 | ], 32 | "primaryKey": { 33 | "columnNames": [ 34 | "id" 35 | ], 36 | "autoGenerate": true 37 | }, 38 | "indices": [], 39 | "foreignKeys": [] 40 | } 41 | ], 42 | "setupQueries": [ 43 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 44 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"1f6c69ff95d18933eb88e034999c59c1\")" 45 | ] 46 | } 47 | } -------------------------------------------------------------------------------- /app/schemas/de.dabotz.shoppinglist.database.AppDatabase/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 2, 5 | "identityHash": "5a04a704af70d1902840afc7e578cf1d", 6 | "entities": [ 7 | { 8 | "tableName": "GroceryListItem", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `count` INTEGER NOT NULL, `price` REAL NOT NULL, `created` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "name", 19 | "columnName": "name", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "count", 25 | "columnName": "count", 26 | "affinity": "INTEGER", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "price", 31 | "columnName": "price", 32 | "affinity": "REAL", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "created", 37 | "columnName": "created", 38 | "affinity": "INTEGER", 39 | "notNull": true 40 | } 41 | ], 42 | "primaryKey": { 43 | "columnNames": [ 44 | "id" 45 | ], 46 | "autoGenerate": true 47 | }, 48 | "indices": [], 49 | "foreignKeys": [] 50 | } 51 | ], 52 | "setupQueries": [ 53 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 54 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"5a04a704af70d1902840afc7e578cf1d\")" 55 | ] 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/de/dabotz/shoppinglist/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.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("de.dabotz.shoppinglist", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/androidTest/java/de/dabotz/shoppinglist/GrocerListItemEntityTest.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist 2 | 3 | import android.arch.lifecycle.Observer 4 | import android.arch.persistence.room.Room 5 | import android.support.test.InstrumentationRegistry 6 | import de.dabotz.shoppinglist.database.AppDatabase 7 | import de.dabotz.shoppinglist.models.GroceryListItem 8 | import org.junit.After 9 | import org.junit.Test 10 | import org.junit.Assert.assertThat 11 | import org.hamcrest.Matchers.equalTo 12 | import java.util.* 13 | 14 | /** 15 | * Created by Botz on 30.07.17. 16 | */ 17 | class GrocerListItemEntityTest { 18 | val context = InstrumentationRegistry.getTargetContext() 19 | val db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build() 20 | val dao = db.groceryListItemDao() 21 | 22 | @After 23 | fun cleanUp() { 24 | db.close() 25 | } 26 | 27 | @Test 28 | fun writeAndReadAsList() { 29 | val item = GroceryListItem(0, "Pizza", 2, 10.9, Date()) 30 | dao.add(item) 31 | 32 | val results = dao.getAllGroceryListItems() 33 | 34 | results.observeForever { items -> assertThat(items!![0], equalTo(item))} 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/de/dabotz/shoppinglist/MigrationTest.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist 2 | 3 | import android.app.Instrumentation 4 | import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory 5 | import android.arch.persistence.room.testing.MigrationTestHelper 6 | import android.support.test.InstrumentationRegistry 7 | import android.support.test.runner.AndroidJUnit4 8 | import de.dabotz.shoppinglist.database.AppDatabase 9 | import de.dabotz.shoppinglist.database.MIGRATION_1_2 10 | import org.junit.Rule 11 | import org.junit.Test 12 | import org.junit.runner.RunWith 13 | 14 | /** 15 | * Created by Botz on 30.07.17. 16 | */ 17 | 18 | @RunWith(AndroidJUnit4::class) 19 | class MigrationTest { 20 | private val TEST_DB = "migration-test" 21 | 22 | @Rule @JvmField 23 | val helper = MigrationTestHelper(InstrumentationRegistry.getInstrumentation(), 24 | AppDatabase::class.java.canonicalName, 25 | FrameworkSQLiteOpenHelperFactory()) 26 | 27 | @Test 28 | fun migrateVersion1to2() { 29 | var db = helper.createDatabase(TEST_DB, 1) 30 | 31 | db.execSQL("INSERT INTO grocerylistitem VALUES (1, 'Test', 2, 100)") 32 | 33 | db.close() 34 | 35 | db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2) 36 | 37 | db.close() 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/Extensions.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | 6 | /** 7 | * Created by Botz on 06.07.17. 8 | */ 9 | 10 | fun ViewGroup.inflate(layoutRes:Int) = LayoutInflater.from(context).inflate(layoutRes, this, false) -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist 2 | 3 | import android.os.Bundle 4 | import android.support.v4.app.FragmentActivity 5 | import com.github.salomonbrys.kodein.Kodein 6 | import com.github.salomonbrys.kodein.android.KodeinFragmentActivity 7 | import com.github.salomonbrys.kodein.bind 8 | import com.github.salomonbrys.kodein.instance 9 | 10 | class MainActivity : KodeinFragmentActivity() { 11 | 12 | override fun provideOverridingModule(): Kodein.Module = Kodein.Module { 13 | bind("Activity") with instance(this@MainActivity) 14 | } 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContentView(R.layout.main_layout) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/ShoppingListApplication.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist 2 | 3 | import android.app.Application 4 | import android.arch.persistence.room.Room 5 | import com.github.salomonbrys.kodein.Kodein 6 | import com.github.salomonbrys.kodein.KodeinAware 7 | import com.github.salomonbrys.kodein.bind 8 | import com.github.salomonbrys.kodein.eagerSingleton 9 | import de.dabotz.shoppinglist.database.AppDatabase 10 | import de.dabotz.shoppinglist.database.MIGRATION_1_2 11 | 12 | /** 13 | * Created by Botz on 24.09.17. 14 | */ 15 | class ShoppingListApplication: Application(), KodeinAware { 16 | override val kodein: Kodein = Kodein { 17 | bind() with eagerSingleton { 18 | Room.databaseBuilder(this@ShoppingListApplication, AppDatabase::class.java, "grocery-db") 19 | .allowMainThreadQueries() 20 | .addMigrations(MIGRATION_1_2) 21 | .build() } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/database/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist.database 2 | 3 | import android.arch.persistence.room.Database 4 | import android.arch.persistence.room.Room 5 | import android.arch.persistence.room.RoomDatabase 6 | import android.arch.persistence.room.TypeConverters 7 | import android.content.Context 8 | import de.dabotz.shoppinglist.models.GroceryListItem 9 | 10 | /** 11 | * Created by Botz on 05.07.17. 12 | */ 13 | @Database(entities = arrayOf(GroceryListItem::class), version = 2) 14 | @TypeConverters(DateConverter::class) 15 | abstract class AppDatabase: RoomDatabase() { 16 | abstract fun groceryListItemDao() : GroceryListItemDAO 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/database/DateConverter.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist.database 2 | 3 | import android.arch.persistence.room.TypeConverter 4 | import java.util.* 5 | 6 | 7 | /** 8 | * Created by Botz on 05.07.17. 9 | */ 10 | class DateConverter { 11 | 12 | @TypeConverter 13 | fun toDate(timestamp: Long?): Date? { 14 | return timestamp?.let { Date(it) } 15 | } 16 | 17 | @TypeConverter 18 | fun toTimestamp(date: Date?): Long? { 19 | return date?.let { it.time } 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/database/GroceryListItemDAO.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist.database 2 | 3 | import android.arch.lifecycle.LiveData 4 | import android.arch.persistence.room.* 5 | import de.dabotz.shoppinglist.models.GroceryListItem 6 | 7 | /** 8 | * Created by Botz on 05.07.17. 9 | */ 10 | @Dao 11 | interface GroceryListItemDAO { 12 | @Query("select * from grocerylistitem") 13 | fun getAllGroceryListItems() : LiveData> 14 | 15 | @Insert 16 | fun add(item: GroceryListItem) 17 | 18 | @Delete 19 | fun delete(groceryListItem: GroceryListItem?) 20 | 21 | @Query("select * from grocerylistitem where id=:id limit 1") 22 | fun find(id: Int): LiveData 23 | 24 | @Update 25 | fun update(groceryListItem: GroceryListItem?) 26 | } -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/database/Migrations.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist.database 2 | 3 | import android.arch.persistence.db.SupportSQLiteDatabase 4 | import android.arch.persistence.room.migration.Migration 5 | 6 | /** 7 | * Created by Botz on 30.07.17. 8 | */ 9 | 10 | val MIGRATION_1_2 = object: Migration(1, 2) { 11 | override fun migrate(database: SupportSQLiteDatabase) { 12 | database.execSQL("ALTER TABLE grocerylistitem ADD COLUMN price REAL") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/models/GroceryListItem.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist.models 2 | 3 | import android.arch.persistence.room.Entity 4 | import android.arch.persistence.room.PrimaryKey 5 | import java.util.* 6 | 7 | /** 8 | * Created by Botz on 05.07.17. 9 | */ 10 | @Entity 11 | data class GroceryListItem(@PrimaryKey(autoGenerate = true) val id: Int, 12 | var name: String = "", 13 | var count: Int = 1, 14 | var price: Double = 1.0, 15 | var created: Date = Date()) -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/models/GroceryListItemViewModel.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist.models 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import android.arch.lifecycle.ViewModelProvider 5 | import de.dabotz.shoppinglist.database.AppDatabase 6 | 7 | /** 8 | * Created by Botz on 05.07.17. 9 | */ 10 | class GroceryListItemViewModel(val appDataBase: AppDatabase): ViewModel() { 11 | 12 | val groceryListItems = appDataBase.groceryListItemDao().getAllGroceryListItems() 13 | 14 | fun addItem(groceryListItem: GroceryListItem) = appDataBase.groceryListItemDao().add(groceryListItem) 15 | fun delete(groceryListItem: GroceryListItem?) = appDataBase.groceryListItemDao().delete(groceryListItem) 16 | fun find(id : Int) = appDataBase.groceryListItemDao().find(id) 17 | fun update(groceryListItem : GroceryListItem) = appDataBase.groceryListItemDao().update(groceryListItem) 18 | 19 | class Fabric(val appDataBase: AppDatabase) : ViewModelProvider.NewInstanceFactory() { 20 | override fun create(modelClass: Class): T = GroceryListItemViewModel(appDataBase) as T 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/models/SelectedId.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist.models 2 | 3 | import android.arch.lifecycle.LiveData 4 | import android.arch.lifecycle.MutableLiveData 5 | import android.arch.lifecycle.ViewModel 6 | 7 | /** 8 | * Created by Botz on 08.07.17. 9 | */ 10 | class SelectedId : ViewModel() { 11 | 12 | val selected: MutableLiveData = MutableLiveData() 13 | 14 | fun select(id: Int) { 15 | selected.value = id 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/modules/detail/DetailActivity.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist.modules.detail 2 | 3 | import android.arch.lifecycle.ViewModelProviders 4 | import android.os.Bundle 5 | import android.support.v4.app.FragmentActivity 6 | import android.support.v7.app.AppCompatActivity 7 | import com.github.salomonbrys.kodein.Kodein 8 | import com.github.salomonbrys.kodein.KodeinInjector 9 | import com.github.salomonbrys.kodein.android.FragmentActivityInjector 10 | import com.github.salomonbrys.kodein.bind 11 | import com.github.salomonbrys.kodein.instance 12 | import de.dabotz.shoppinglist.R 13 | import de.dabotz.shoppinglist.models.SelectedId 14 | 15 | class DetailActivity : AppCompatActivity(), FragmentActivityInjector { 16 | override val injector: KodeinInjector = KodeinInjector() 17 | 18 | override fun provideOverridingModule() = Kodein.Module { 19 | bind("Activity") with instance(this@DetailActivity) 20 | } 21 | 22 | val selectedViewModel by lazy { 23 | ViewModelProviders.of(this).get(SelectedId::class.java) 24 | } 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | initializeInjector() 29 | if(resources.getBoolean(R.bool.has_two_panes)) { 30 | finish() 31 | return 32 | } 33 | selectedViewModel.select(intent.extras.getInt("itemId")) 34 | 35 | setContentView(R.layout.detail_layout) 36 | } 37 | 38 | override fun onDestroy() { 39 | destroyInjector() 40 | super.onDestroy() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/modules/detail/DetailModule.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist.modules.detail 2 | 3 | import android.arch.lifecycle.ViewModelProviders 4 | import android.support.v4.app.Fragment 5 | import android.support.v4.app.FragmentActivity 6 | import com.github.salomonbrys.kodein.Kodein 7 | import com.github.salomonbrys.kodein.bind 8 | import com.github.salomonbrys.kodein.instance 9 | import com.github.salomonbrys.kodein.provider 10 | import de.dabotz.shoppinglist.models.GroceryListItemViewModel 11 | import de.dabotz.shoppinglist.models.SelectedId 12 | 13 | /** 14 | * Created by Botz on 24.09.17. 15 | */ 16 | fun createDetailModule(fragment: Fragment) = Kodein.Module { 17 | bind() with provider { 18 | val factory = GroceryListItemViewModel.Fabric(instance()) 19 | ViewModelProviders.of(fragment, factory)[GroceryListItemViewModel::class.java] 20 | } 21 | 22 | bind() with provider { 23 | ViewModelProviders.of(instance("Activity"))[SelectedId::class.java] 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/modules/detail/ShoppingItemFragment.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist.modules.detail 2 | 3 | import android.arch.lifecycle.Observer 4 | import android.arch.lifecycle.Transformations 5 | import android.databinding.DataBindingUtil 6 | import android.os.Bundle 7 | import android.support.v4.app.Fragment 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import com.github.salomonbrys.kodein.KodeinInjector 12 | import com.github.salomonbrys.kodein.android.SupportFragmentInjector 13 | import com.github.salomonbrys.kodein.instance 14 | import de.dabotz.shoppinglist.R 15 | import de.dabotz.shoppinglist.databinding.FShoppingItemBinding 16 | import de.dabotz.shoppinglist.models.GroceryListItem 17 | import de.dabotz.shoppinglist.models.GroceryListItemViewModel 18 | import de.dabotz.shoppinglist.models.SelectedId 19 | 20 | /** 21 | * Created by Botz on 08.07.17. 22 | */ 23 | class ShoppingItemFragment: Fragment(), SupportFragmentInjector { 24 | override val injector: KodeinInjector = KodeinInjector() 25 | 26 | 27 | val viewModel: GroceryListItemViewModel by injector.instance() 28 | val selectedId: SelectedId by injector.instance() 29 | 30 | override fun provideOverridingModule() = createDetailModule(this) 31 | 32 | lateinit var dataBinding: FShoppingItemBinding 33 | 34 | 35 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 36 | initializeInjector() 37 | dataBinding = DataBindingUtil.inflate(inflater, R.layout.f_shopping_item, container,false) 38 | dataBinding.handler = this 39 | return dataBinding.root 40 | } 41 | 42 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 43 | super.onViewCreated(view, savedInstanceState) 44 | 45 | 46 | Transformations.switchMap(selectedId.selected) { selectedId -> 47 | viewModel.find(selectedId) 48 | }?.observe(this, Observer { 49 | println("viewModel observer ${it?.id}") 50 | dataBinding.groceryItem = it 51 | }) 52 | } 53 | 54 | fun increase(view:View, item:GroceryListItem) { 55 | println("increase ${item.id}") 56 | item.count++ 57 | viewModel.update(item) 58 | } 59 | 60 | fun decrease(view:View, item:GroceryListItem) { 61 | println("decrease ${item.id}") 62 | item.count-- 63 | viewModel.update(item) 64 | } 65 | 66 | override fun onDestroy() { 67 | destroyInjector() 68 | super.onDestroy() 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/modules/list/GroceryListItemAdapter.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist.modules.list 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import de.dabotz.shoppinglist.R 7 | import de.dabotz.shoppinglist.inflate 8 | import de.dabotz.shoppinglist.models.GroceryListItem 9 | import kotlinx.android.synthetic.main.vh_grocerylist.view.* 10 | 11 | /** 12 | * Created by Botz on 06.07.17. 13 | */ 14 | class GroceryListItemAdapter(val onClickListener: (item:GroceryListItem) -> Unit): RecyclerView.Adapter() { 15 | 16 | var data: List = listOf() 17 | set(value) { 18 | field = value 19 | notifyDataSetChanged() 20 | } 21 | 22 | override fun onCreateViewHolder(parent: ViewGroup, position: Int) = GroceryListItemViewHolder(parent.inflate(R.layout.vh_grocerylist)) 23 | 24 | override fun onBindViewHolder(viewHolder: GroceryListItemViewHolder, position: Int) { 25 | //println("Bind ViewHolder Position: ${position}") 26 | viewHolder.bindView(data[position], onClickListener) 27 | } 28 | 29 | override fun getItemCount(): Int { 30 | //println("Item Size: ${data.size}") 31 | return data.size 32 | } 33 | 34 | 35 | class GroceryListItemViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { 36 | 37 | fun bindView(groceryListItem: GroceryListItem, onClickListener: (item: GroceryListItem) -> Unit) { 38 | itemView.groceryListItemName.text = groceryListItem.name 39 | itemView.groceryListItemQuantity.text = groceryListItem.count.toString() 40 | itemView.setOnClickListener { 41 | onClickListener(groceryListItem) 42 | } 43 | } 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/modules/list/ListModule.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist.modules.list 2 | 3 | import android.arch.lifecycle.ViewModelProviders 4 | import android.support.v4.app.Fragment 5 | import android.support.v4.app.FragmentActivity 6 | import com.github.salomonbrys.kodein.Kodein 7 | import com.github.salomonbrys.kodein.bind 8 | import com.github.salomonbrys.kodein.instance 9 | import com.github.salomonbrys.kodein.provider 10 | import de.dabotz.shoppinglist.models.GroceryListItemViewModel 11 | import de.dabotz.shoppinglist.models.SelectedId 12 | 13 | /** 14 | * Created by Botz on 24.09.17. 15 | */ 16 | 17 | fun createListModule(fragment: Fragment) = Kodein.Module { 18 | bind() with provider { 19 | val factory = GroceryListItemViewModel.Fabric(instance()) 20 | ViewModelProviders.of(fragment, factory)[GroceryListItemViewModel::class.java] 21 | } 22 | 23 | bind() with provider { 24 | ViewModelProviders.of(instance("Activity"))[SelectedId::class.java] 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/de/dabotz/shoppinglist/modules/list/ShoppingListFragment.kt: -------------------------------------------------------------------------------- 1 | package de.dabotz.shoppinglist.modules.list 2 | 3 | import android.arch.lifecycle.Observer 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.support.v4.app.Fragment 7 | import android.support.v7.widget.DividerItemDecoration 8 | import android.support.v7.widget.LinearLayoutManager 9 | import android.support.v7.widget.RecyclerView 10 | import android.support.v7.widget.helper.ItemTouchHelper 11 | import android.view.LayoutInflater 12 | import android.view.View 13 | import android.view.ViewGroup 14 | import com.github.salomonbrys.kodein.KodeinInjector 15 | import com.github.salomonbrys.kodein.android.SupportFragmentInjector 16 | import com.github.salomonbrys.kodein.instance 17 | import de.dabotz.shoppinglist.R 18 | import de.dabotz.shoppinglist.models.GroceryListItem 19 | import de.dabotz.shoppinglist.models.GroceryListItemViewModel 20 | import de.dabotz.shoppinglist.models.SelectedId 21 | import de.dabotz.shoppinglist.modules.detail.DetailActivity 22 | import kotlinx.android.synthetic.main.f_shopping_list.* 23 | import java.util.* 24 | 25 | 26 | /** 27 | * Created by Botz on 08.07.17. 28 | */ 29 | class ShoppingListFragment: Fragment(), SupportFragmentInjector { 30 | override val injector: KodeinInjector = KodeinInjector() 31 | 32 | val groceryList by lazy { 33 | groceryItemsRecyclerView.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) 34 | groceryItemsRecyclerView.adapter = groceryListItemAdapter 35 | val itemDecoration = DividerItemDecoration(context, DividerItemDecoration.VERTICAL) 36 | groceryItemsRecyclerView.addItemDecoration(itemDecoration) 37 | groceryItemsRecyclerView 38 | } 39 | 40 | val groceryListItemAdapter = GroceryListItemAdapter { 41 | selectedViewModel.select(it.id) 42 | val intent = Intent(context, DetailActivity::class.java) 43 | intent.putExtra("itemId", it.id) 44 | startActivity(intent) 45 | } 46 | 47 | val itemTouchHelper = ItemTouchHelper(object: ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { 48 | override fun onMove(p0: RecyclerView?, p1: RecyclerView.ViewHolder?, p2: RecyclerView.ViewHolder?): Boolean { 49 | return true 50 | } 51 | 52 | override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) { 53 | viewModel.delete(viewModel.groceryListItems.value?.get(viewHolder.adapterPosition)) 54 | } 55 | }) 56 | 57 | val viewModel: GroceryListItemViewModel by injector.instance() 58 | val selectedViewModel: SelectedId by injector.instance() 59 | 60 | override fun provideOverridingModule() = createListModule(this) 61 | 62 | 63 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 64 | initializeInjector() 65 | return inflater!!.inflate(R.layout.f_shopping_list, container, false) 66 | } 67 | 68 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 69 | super.onViewCreated(view, savedInstanceState) 70 | itemTouchHelper.attachToRecyclerView(groceryList) 71 | 72 | viewModel.groceryListItems.observe(this, Observer { items -> 73 | items?.let { 74 | groceryListItemAdapter.data = it 75 | } 76 | }) 77 | 78 | sendButton.setOnClickListener { 79 | viewModel.addItem(GroceryListItem(0, 80 | groceryItemEditText.text.toString(), 1, created = Date())) 81 | groceryItemEditText.text.clear() 82 | } 83 | } 84 | 85 | override fun onDestroy() { 86 | destroyInjector() 87 | super.onDestroy() 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/detail_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dual_pane.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/f_shopping_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 13 | 14 | 15 | 16 | 21 | 22 | 35 | 36 |