diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 000000000..d1095bd3c --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,25 @@ +/* Remove this comment + +Reporting an issue with the Architecture Components or the samples? + +## Question about Architecture Components? + +If you want to ask how to do something, or to understand why something isn't working the way you expect it to use Stack Overflow. Post your questions using these tags: +* Questions about the architecture guide or using components together: android-arch https://stackoverflow.com/questions/tagged/android-arch +* Questions about the Room library: android-room https://stackoverflow.com/questions/tagged/android-room +* Questions about the Lifecycle library: android-arch-lifecycle https://stackoverflow.com/questions/tagged/android-arch-lifecycle + + +## Bug report or feature request for Architecture Components? + +If you find an issue with the Architecture Components libraries or consider that a feature is missing, report it using the Google issue tracker: https://issuetracker.google.com/issues/new?component=197448&template=878802 + +Before filing a new issue, please check if it is listed in the Release Notes or reported in the issues list. +* Release Notes: https://developer.android.com/topic/libraries/architecture/release-notes.html +* Issues list: https://issuetracker.google.com/issues?q=componentid=197448%20status:open&s=modified_time:desc + +## Issue with the samples? + +Please add the sample name ("all", "GithubBrowserSample", "BasicRxJavaSampleKotlin", etc.) and include it in the title if it applies. + +*/ diff --git a/BasicRxJavaSample/.gitignore b/BasicRxJavaSample/.gitignore new file mode 100644 index 000000000..39fb081a4 --- /dev/null +++ b/BasicRxJavaSample/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/BasicRxJavaSample/.google/packaging.yaml b/BasicRxJavaSample/.google/packaging.yaml new file mode 100644 index 000000000..108903dd1 --- /dev/null +++ b/BasicRxJavaSample/.google/packaging.yaml @@ -0,0 +1,41 @@ +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# GOOGLE SAMPLE PACKAGING DATA +# +# This file is used by Google as part of our samples packaging process. +# End users may safely ignore this file. It has no relevance to other systems. +--- +# Values: {DRAFT | PUBLISHED | INTERNAL | DEPRECATED | SUPERCEDED} +status: PUBLISHED + +# Optional, put additional explanation here for DEPRECATED or SUPERCEDED. +# statusNote: + +# See http://go/sample-categories +technologies: [Android] +categories: [Architecture] +languages: [Java] +solutions: [Mobile] + +# May be omitted if unpublished +github: googlesamples/android-architecture-components + +# Values: BEGINNER | INTERMEDIATE | ADVANCED | EXPERT +level: ADVANCED + +# Default: apache2. May be omitted for most samples. +# Alternatives: apache2-android (for AOSP) +license: apache2 diff --git a/BasicRxJavaSample/README.md b/BasicRxJavaSample/README.md new file mode 100644 index 000000000..20841ce63 --- /dev/null +++ b/BasicRxJavaSample/README.md @@ -0,0 +1,53 @@ +Room & RxJava Sample +===================== + +This is an API sample to showcase how to implement observable queries in +[Room](https://developer.android.com/topic/libraries/architecture/room.html), with RxJava's +[Flowable](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html) objects. + +Introduction +------------- + +### Functionality +The sample app shows an editable user name, stored in the database. + +### Implementation + +#### Data layer + +The database is created using Room and has one entity: a `User`. Room generates the corresponding SQLite table at +runtime. + +Queries are executed in the `UserDao` class. The user retrieval is done via an observable query implemented using a +`Flowable`. Every time the user data is updated, the Flowable object will emit automatically, allowing to update the UI +based on the latest data. The Flowable will emit only when the query result contains at least a row. When there is no +data to match the query, the Flowable will not emit. + +#### Presentation layer + +The app has a main Activity that displays the data. +The Activity works with a ViewModel to do the following: +* subscribe to the emissions of the user name and update the UI every time there is a new user name emitted +* notify the ViewModel when the "Update" button is pressed and pass the new user name. +The ViewModel works with the data source to get and save the data. + +Room guarantees that the observable query will be triggered on a background thread. In the Activity, the Flowable events +are set to be received on the main thread, so the UI can be updated. The insert query is synchronous so it's wrapped in +a Completable and executed on a background thread. On completion, the Activity is notified on the main thread. + +License +-------- + +Copyright (C) 2017 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/BasicRxJavaSample/app/.gitignore b/BasicRxJavaSample/app/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/BasicRxJavaSample/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/BasicRxJavaSample/app/build.gradle b/BasicRxJavaSample/app/build.gradle new file mode 100644 index 000000000..9de3fd881 --- /dev/null +++ b/BasicRxJavaSample/app/build.gradle @@ -0,0 +1,77 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion build_versions.target_sdk + buildToolsVersion build_versions.build_tools + + defaultConfig { + applicationId "com.example.android.persistence" + minSdkVersion build_versions.min_sdk + targetSdkVersion build_versions.target_sdk + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + // Support libraries + compile deps.support.app_compat + compile deps.support.v4 + compile deps.support.design + // Architecture components + compile deps.lifecycle.runtime + compile deps.lifecycle.extensions + annotationProcessor deps.lifecycle.compiler + compile deps.room.runtime + annotationProcessor deps.room.compiler + compile deps.room.rxjava2 + + // RxJava + compile deps.rx_android + compile deps.rxjava2 + + // Dependencies for local unit tests + testCompile deps.junit + testCompile deps.hamcrest + testCompile deps.arch_core.testing + testCompile deps.mockito.all + + // Resolve conflicts between main and local unit tests + testCompile deps.support.annotations + testCompile deps.support.core_utils + + // Android Testing Support Library's runner and rules + androidTestCompile deps.atsl.runner + androidTestCompile deps.atsl.rules + androidTestCompile deps.room.testing + androidTestCompile deps.arch_core.testing + + // Dependencies for Android unit tests + androidTestCompile deps.junit + androidTestCompile deps.mockito.core, { exclude group: 'net.bytebuddy' } + androidTestCompile deps.dexmaker + + // Espresso UI Testing + androidTestCompile deps.espresso.core + androidTestCompile deps.espresso.contrib + androidTestCompile deps.espresso.intents + + // Resolve conflicts between main and test APK: + androidTestCompile deps.support.annotations + androidTestCompile deps.support.v4 + androidTestCompile deps.support.app_compat + androidTestCompile deps.support.design +} diff --git a/BasicRxJavaSample/app/proguard-rules.pro b/BasicRxJavaSample/app/proguard-rules.pro new file mode 100644 index 000000000..5e78d02af --- /dev/null +++ b/BasicRxJavaSample/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/florinam/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/BasicRxJavaSample/app/src/androidTest/java/com/example/android/observability/persistence/LocalUserDataSourceTest.java b/BasicRxJavaSample/app/src/androidTest/java/com/example/android/observability/persistence/LocalUserDataSourceTest.java new file mode 100644 index 000000000..a5febcd21 --- /dev/null +++ b/BasicRxJavaSample/app/src/androidTest/java/com/example/android/observability/persistence/LocalUserDataSourceTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.observability.persistence; + +import android.arch.core.executor.testing.InstantTaskExecutorRule; +import android.arch.persistence.room.Room; +import android.support.test.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +/** + * Integration tests for the {@link LocalUserDataSource} implementation with Room. + */ +public class LocalUserDataSourceTest { + + @Rule + public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule(); + + private static final User USER = new User("id", "username"); + + private UsersDatabase mDatabase; + private LocalUserDataSource mDataSource; + + @Before + public void initDb() throws Exception { + // using an in-memory database because the information stored here disappears when the + // process is killed + mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(), + UsersDatabase.class) + // allowing main thread queries, just for testing + .allowMainThreadQueries() + .build(); + mDataSource = new LocalUserDataSource(mDatabase.userDao()); + } + + @After + public void closeDb() throws Exception { + mDatabase.close(); + } + + @Test + public void insertAndGetUser() { + // When inserting a new user in the data source + mDataSource.insertOrUpdateUser(USER); + + // When subscribing to the emissions of the user + mDataSource.getUser() + .test() + // assertValue asserts that there was only one emission of the user + .assertValue(user -> { + // The emitted user is the expected one + return user != null && user.getId().equals(USER.getId()) && + user.getUserName().equals(USER.getUserName()); + }); + } + + @Test + public void updateAndGetUser() { + // Given that we have a user in the data source + mDataSource.insertOrUpdateUser(USER); + + // When we are updating the name of the user + User updatedUser = new User(USER.getId(), "new username"); + mDataSource.insertOrUpdateUser(updatedUser); + + // When subscribing to the emissions of the user + mDatabase.userDao().getUser() + .test() + // assertValue asserts that there was only one emission of the user + .assertValue(user -> { + // The emitted user is the expected one + return user != null && user.getId().equals(USER.getId()) && + user.getUserName().equals("new username"); + }); + } + + @Test + public void deleteAndGetUser() { + // Given that we have a user in the data source + mDataSource.insertOrUpdateUser(USER); + + //When we are deleting all users + mDataSource.deleteAllUsers(); + // When subscribing to the emissions of the user + mDatabase.userDao().getUser() + .test() + // check that there's no user emitted + .assertNoValues(); + } +} diff --git a/BasicRxJavaSample/app/src/androidTest/java/com/example/android/observability/persistence/UserDaoTest.java b/BasicRxJavaSample/app/src/androidTest/java/com/example/android/observability/persistence/UserDaoTest.java new file mode 100644 index 000000000..10dc52bf5 --- /dev/null +++ b/BasicRxJavaSample/app/src/androidTest/java/com/example/android/observability/persistence/UserDaoTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.observability.persistence; + +import android.arch.core.executor.testing.InstantTaskExecutorRule; +import android.arch.persistence.room.Room; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test the implementation of {@link UserDao} + */ +@RunWith(AndroidJUnit4.class) +public class UserDaoTest { + + @Rule + public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule(); + + private static final User USER = new User("id", "username"); + + private UsersDatabase mDatabase; + + @Before + public void initDb() throws Exception { + // using an in-memory database because the information stored here disappears when the + // process is killed + mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(), + UsersDatabase.class) + // allowing main thread queries, just for testing + .allowMainThreadQueries() + .build(); + } + + @After + public void closeDb() throws Exception { + mDatabase.close(); + } + + @Test + public void getUsersWhenNoUserInserted() { + mDatabase.userDao().getUser() + .test() + .assertNoValues(); + } + + @Test + public void insertAndGetUser() { + // When inserting a new user in the data source + mDatabase.userDao().insertUser(USER); + + // When subscribing to the emissions of the user + mDatabase.userDao().getUser() + .test() + // assertValue asserts that there was only one emission of the user + .assertValue(user -> { + // The emitted user is the expected one + return user != null && user.getId().equals(USER.getId()) && + user.getUserName().equals(USER.getUserName()); + }); + } + + @Test + public void updateAndGetUser() { + // Given that we have a user in the data source + mDatabase.userDao().insertUser(USER); + + // When we are updating the name of the user + User updatedUser = new User(USER.getId(), "new username"); + mDatabase.userDao().insertUser(updatedUser); + + // When subscribing to the emissions of the user + mDatabase.userDao().getUser() + .test() + // assertValue asserts that there was only one emission of the user + .assertValue(user -> { + // The emitted user is the expected one + return user != null && user.getId().equals(USER.getId()) && + user.getUserName().equals("new username"); + }); + } + + @Test + public void deleteAndGetUser() { + // Given that we have a user in the data source + mDatabase.userDao().insertUser(USER); + + //When we are deleting all users + mDatabase.userDao().deleteAllUsers(); + // When subscribing to the emissions of the user + mDatabase.userDao().getUser() + .test() + // check that there's no user emitted + .assertNoValues(); + } +} diff --git a/BasicRxJavaSample/app/src/main/AndroidManifest.xml b/BasicRxJavaSample/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..06bff3124 --- /dev/null +++ b/BasicRxJavaSample/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BasicRxJavaSample/app/src/main/java/com/example/android/observability/Injection.java b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/Injection.java new file mode 100644 index 000000000..d3f3a2e4e --- /dev/null +++ b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/Injection.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.observability; + +import android.content.Context; + +import com.example.android.observability.persistence.LocalUserDataSource; +import com.example.android.observability.persistence.UsersDatabase; +import com.example.android.observability.ui.ViewModelFactory; + +/** + * Enables injection of data sources. + */ +public class Injection { + + public static UserDataSource provideUserDataSource(Context context) { + UsersDatabase database = UsersDatabase.getInstance(context); + return new LocalUserDataSource(database.userDao()); + } + + public static ViewModelFactory provideViewModelFactory(Context context) { + UserDataSource dataSource = provideUserDataSource(context); + return new ViewModelFactory(dataSource); + } +} diff --git a/BasicRxJavaSample/app/src/main/java/com/example/android/observability/UserDataSource.java b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/UserDataSource.java new file mode 100644 index 000000000..cd2f6010b --- /dev/null +++ b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/UserDataSource.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.observability; + +import com.example.android.observability.persistence.User; + +import io.reactivex.Flowable; + +/** + * Access point for managing user data. + */ +public interface UserDataSource { + + /** + * Gets the user from the data source. + * + * @return the user from the data source. + */ + Flowable getUser(); + + /** + * Inserts the user into the data source, or, if this is an existing user, updates it. + * + * @param user the user to be inserted or updated. + */ + void insertOrUpdateUser(User user); + + /** + * Deletes all users from the data source. + */ + void deleteAllUsers(); +} \ No newline at end of file diff --git a/BasicRxJavaSample/app/src/main/java/com/example/android/observability/persistence/LocalUserDataSource.java b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/persistence/LocalUserDataSource.java new file mode 100644 index 000000000..621e343b8 --- /dev/null +++ b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/persistence/LocalUserDataSource.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.observability.persistence; + +import com.example.android.observability.UserDataSource; + +import io.reactivex.Flowable; + +/** + * Using the Room database as a data source. + */ +public class LocalUserDataSource implements UserDataSource { + + private final UserDao mUserDao; + + public LocalUserDataSource(UserDao userDao) { + mUserDao = userDao; + } + + @Override + public Flowable getUser() { + return mUserDao.getUser(); + } + + @Override + public void insertOrUpdateUser(User user) { + mUserDao.insertUser(user); + } + + @Override + public void deleteAllUsers() { + mUserDao.deleteAllUsers(); + } +} diff --git a/BasicRxJavaSample/app/src/main/java/com/example/android/observability/persistence/User.java b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/persistence/User.java new file mode 100644 index 000000000..90d3a102e --- /dev/null +++ b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/persistence/User.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.observability.persistence; + +import android.arch.persistence.room.ColumnInfo; +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.Ignore; +import android.arch.persistence.room.PrimaryKey; +import android.support.annotation.NonNull; + +import java.util.UUID; + +/** + * Immutable model class for a User + */ +@Entity(tableName = "users") +public class User { + + @NonNull + @PrimaryKey + @ColumnInfo(name = "userid") + private String mId; + + @ColumnInfo(name = "username") + private String mUserName; + + @Ignore + public User(String userName) { + mId = UUID.randomUUID().toString(); + mUserName = userName; + } + + public User(String id, String userName) { + this.mId = id; + this.mUserName = userName; + } + + public String getId() { + return mId; + } + + public String getUserName() { + return mUserName; + } +} diff --git a/BasicRxJavaSample/app/src/main/java/com/example/android/observability/persistence/UserDao.java b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/persistence/UserDao.java new file mode 100644 index 000000000..eb4af00e2 --- /dev/null +++ b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/persistence/UserDao.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.observability.persistence; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; + +import io.reactivex.Flowable; + +/** + * Data Access Object for the users table. + */ +@Dao +public interface UserDao { + + /** + * Get the user from the table. Since for simplicity we only have one user in the database, + * this query gets all users from the table, but limits the result to just the 1st user. + * + * @return the user from the table + */ + @Query("SELECT * FROM Users LIMIT 1") + Flowable getUser(); + + /** + * Insert a user in the database. If the user already exists, replace it. + * + * @param user the user to be inserted. + */ + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insertUser(User user); + + /** + * Delete all users. + */ + @Query("DELETE FROM Users") + void deleteAllUsers(); +} diff --git a/BasicRxJavaSample/app/src/main/java/com/example/android/observability/persistence/UsersDatabase.java b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/persistence/UsersDatabase.java new file mode 100644 index 000000000..028f39849 --- /dev/null +++ b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/persistence/UsersDatabase.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.observability.persistence; + +import android.arch.persistence.room.Database; +import android.arch.persistence.room.Room; +import android.arch.persistence.room.RoomDatabase; +import android.content.Context; + +/** + * The Room database that contains the Users table + */ +@Database(entities = {User.class}, version = 1) +public abstract class UsersDatabase extends RoomDatabase { + + private static volatile UsersDatabase INSTANCE; + + public abstract UserDao userDao(); + + public static UsersDatabase getInstance(Context context) { + if (INSTANCE == null) { + synchronized (UsersDatabase.class) { + if (INSTANCE == null) { + INSTANCE = Room.databaseBuilder(context.getApplicationContext(), + UsersDatabase.class, "Sample.db") + .build(); + } + } + } + return INSTANCE; + } + +} diff --git a/BasicRxJavaSample/app/src/main/java/com/example/android/observability/ui/UserActivity.java b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/ui/UserActivity.java new file mode 100644 index 000000000..fe60b3d79 --- /dev/null +++ b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/ui/UserActivity.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.observability.ui; + +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import com.example.android.observability.Injection; +import com.example.android.persistence.R; + +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; + + +/** + * Main screen of the app. Displays a user name and gives the option to update the user name. + */ +public class UserActivity extends AppCompatActivity { + + private static final String TAG = UserActivity.class.getSimpleName(); + + private TextView mUserName; + + private EditText mUserNameInput; + + private Button mUpdateButton; + + private ViewModelFactory mViewModelFactory; + + private UserViewModel mViewModel; + + private final CompositeDisposable mDisposable = new CompositeDisposable(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_user); + + mUserName = findViewById(R.id.user_name); + mUserNameInput = findViewById(R.id.user_name_input); + mUpdateButton = findViewById(R.id.update_user); + + mViewModelFactory = Injection.provideViewModelFactory(this); + mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(UserViewModel.class); + mUpdateButton.setOnClickListener(v -> updateUserName()); + } + + @Override + protected void onStart() { + super.onStart(); + // Subscribe to the emissions of the user name from the view model. + // Update the user name text view, at every onNext emission. + // In case of error, log the exception. + mDisposable.add(mViewModel.getUserName() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(userName -> mUserName.setText(userName), + throwable -> Log.e(TAG, "Unable to update username", throwable))); + } + + @Override + protected void onStop() { + super.onStop(); + + // clear all the subscriptions + mDisposable.clear(); + } + + private void updateUserName() { + String userName = mUserNameInput.getText().toString(); + // Disable the update button until the user name update has been done + mUpdateButton.setEnabled(false); + // Subscribe to updating the user name. + // Re-enable the button once the user name has been updated + mDisposable.add(mViewModel.updateUserName(userName) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> mUpdateButton.setEnabled(true), + throwable -> Log.e(TAG, "Unable to update username", throwable))); + } +} diff --git a/BasicRxJavaSample/app/src/main/java/com/example/android/observability/ui/UserViewModel.java b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/ui/UserViewModel.java new file mode 100644 index 000000000..9a9733153 --- /dev/null +++ b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/ui/UserViewModel.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.observability.ui; + +import android.arch.lifecycle.ViewModel; +import com.example.android.observability.UserDataSource; +import com.example.android.observability.persistence.User; +import io.reactivex.Completable; +import io.reactivex.Flowable; + +/** + * View Model for the {@link UserActivity} + */ +public class UserViewModel extends ViewModel { + + private final UserDataSource mDataSource; + + private User mUser; + + public UserViewModel(UserDataSource dataSource) { + mDataSource = dataSource; + } + + /** + * Get the user name of the user. + * + * @return a {@link Flowable} that will emit every time the user name has been updated. + */ + public Flowable getUserName() { + return mDataSource.getUser() + // for every emission of the user, get the user name + .map(user -> { + mUser = user; + return user.getUserName(); + }); + + } + + /** + * Update the user name. + * + * @param userName the new user name + * @return a {@link Completable} that completes when the user name is updated + */ + public Completable updateUserName(final String userName) { + return Completable.fromAction(() -> { + // if there's no use, create a new user. + // if we already have a user, then, since the user object is immutable, + // create a new user, with the id of the previous user and the updated user name. + mUser = mUser == null + ? new User(userName) + : new User(mUser.getId(), userName); + + mDataSource.insertOrUpdateUser(mUser); + }); + } +} diff --git a/BasicRxJavaSample/app/src/main/java/com/example/android/observability/ui/ViewModelFactory.java b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/ui/ViewModelFactory.java new file mode 100644 index 000000000..87e512827 --- /dev/null +++ b/BasicRxJavaSample/app/src/main/java/com/example/android/observability/ui/ViewModelFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.observability.ui; + +import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProvider; + +import com.example.android.observability.UserDataSource; + +/** + * Factory for ViewModels + */ +public class ViewModelFactory implements ViewModelProvider.Factory { + + private final UserDataSource mDataSource; + + public ViewModelFactory(UserDataSource dataSource) { + mDataSource = dataSource; + } + + @Override + public T create(Class modelClass) { + if (modelClass.isAssignableFrom(UserViewModel.class)) { + return (T) new UserViewModel(mDataSource); + } + throw new IllegalArgumentException("Unknown ViewModel class"); + } +} diff --git a/BasicRxJavaSample/app/src/main/res/layout/activity_user.xml b/BasicRxJavaSample/app/src/main/res/layout/activity_user.xml new file mode 100644 index 000000000..c73ef3d24 --- /dev/null +++ b/BasicRxJavaSample/app/src/main/res/layout/activity_user.xml @@ -0,0 +1,26 @@ + + + + + + + +