├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── lint.xml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── star_zero │ │ └── example │ │ └── androidmvvm │ │ ├── MockApp.java │ │ ├── MockTestRunner.java │ │ ├── di │ │ ├── MockAppComponent.java │ │ ├── MockAppModule.java │ │ └── MockTaskRepository.java │ │ ├── matcher │ │ └── InputTextLayoutMatcher.java │ │ └── presentation │ │ ├── add_edit_task │ │ └── AddEditTaskActivityTest.java │ │ └── tasks │ │ └── TasksActivityTest.java │ ├── debug │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── star_zero │ │ └── example │ │ └── androidmvvm │ │ └── DebugApp.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── star_zero │ │ │ └── example │ │ │ └── androidmvvm │ │ │ ├── App.java │ │ │ ├── application │ │ │ ├── TaskService.java │ │ │ └── dto │ │ │ │ └── TaskDTO.java │ │ │ ├── di │ │ │ ├── AppComponent.java │ │ │ └── AppModule.java │ │ │ ├── domain │ │ │ ├── Entity.java │ │ │ ├── Identifier.java │ │ │ └── task │ │ │ │ ├── Task.java │ │ │ │ ├── TaskId.java │ │ │ │ ├── TaskRepository.java │ │ │ │ └── TaskValidator.java │ │ │ ├── infrastructure │ │ │ └── task │ │ │ │ ├── RealmTaskRepository.java │ │ │ │ └── TaskTable.java │ │ │ └── presentation │ │ │ ├── add_edit_task │ │ │ ├── AddEditTaskActivity.java │ │ │ └── AddEditTaskViewModel.java │ │ │ ├── shared │ │ │ ├── binding │ │ │ │ └── TextInputLayoutBinding.java │ │ │ └── view │ │ │ │ └── BaseActivity.java │ │ │ └── tasks │ │ │ ├── TasksActivity.java │ │ │ ├── TasksViewModel.java │ │ │ └── adapter │ │ │ ├── ItemTaskViewModel.java │ │ │ └── TasksAdapter.java │ └── res │ │ ├── drawable │ │ ├── ic_add.xml │ │ └── ic_done.xml │ │ ├── layout │ │ ├── activity_add_edit_task.xml │ │ ├── activity_tasks.xml │ │ └── item_task.xml │ │ ├── menu │ │ └── menu_add_edit_task.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── star_zero │ └── example │ └── androidmvvm │ ├── application │ └── TaskServiceTest.java │ ├── di │ └── MockDefaultTaskRepository.java │ ├── domain │ └── task │ │ ├── TaskTest.java │ │ └── TaskValidatorTest.java │ └── helper │ └── HotTestSubscriber.java ├── 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 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AndroidMVVM 2 | === 3 | 4 | Simple TODO app 5 | 6 | * Java 7 | * RxJava1 8 | * MVVM 9 | * DDD 10 | * Dagger2 11 | 12 | ## Branches 13 | 14 | * [master](https://github.com/STAR-ZERO/AndroidMVVM/tree/master) 15 | * Java 16 | * RxJava1(want to migrate to RxJava2) 17 | * [kotlin](https://github.com/STAR-ZERO/AndroidMVVM/tree/kotlin) 18 | * Kotlin 19 | * RxJava2 -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'me.tatarka.retrolambda' 3 | apply plugin: 'realm-android' 4 | 5 | repositories { 6 | mavenCentral() 7 | maven { 8 | url 'https://github.com/uPhyca/stetho-realm/raw/master/maven-repo' 9 | } 10 | } 11 | 12 | android { 13 | compileSdkVersion 25 14 | buildToolsVersion "25.0.2" 15 | defaultConfig { 16 | applicationId "com.star_zero.example.androidmvvm" 17 | minSdkVersion 19 18 | targetSdkVersion 25 19 | versionCode 1 20 | versionName "1.0" 21 | 22 | testInstrumentationRunner "com.star_zero.example.androidmvvm.MockTestRunner" 23 | 24 | // for EventBus 25 | javaCompileOptions { 26 | annotationProcessorOptions { 27 | arguments = [ eventBusIndex : 'com.star_zero.example.androidmvvm.EventBusIndex' ] 28 | } 29 | } 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | 38 | lintOptions { 39 | lintConfig project.file("lint.xml") 40 | } 41 | 42 | dataBinding { 43 | enabled = true 44 | } 45 | 46 | compileOptions { 47 | sourceCompatibility JavaVersion.VERSION_1_8 48 | targetCompatibility JavaVersion.VERSION_1_8 49 | } 50 | 51 | packagingOptions { 52 | exclude 'META-INF/LICENSE.txt' 53 | exclude 'META-INF/NOTICE.txt' 54 | } 55 | 56 | } 57 | 58 | dependencies { 59 | compile fileTree(dir: 'libs', include: ['*.jar']) 60 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 61 | exclude group: 'com.android.support', module: 'support-annotations' 62 | }) 63 | androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.2', { 64 | exclude group: 'com.android.support', module: 'recyclerview-v7' 65 | exclude group: 'com.android.support', module: 'appcompat-v7' 66 | exclude group: 'com.android.support', module: 'support-v4' 67 | exclude group: 'com.android.support', module: 'design' 68 | }) 69 | androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2' 70 | androidTestAnnotationProcessor 'com.google.dagger:dagger-compiler:2.9' 71 | 72 | testCompile 'junit:junit:4.12' 73 | testCompile 'org.robolectric:robolectric:3.2.2' 74 | testCompile 'joda-time:joda-time:2.9.5' 75 | 76 | compile 'com.android.support:appcompat-v7:25.2.0' 77 | compile 'com.android.support:design:25.2.0' 78 | 79 | compile 'com.jakewharton.timber:timber:4.5.1' 80 | 81 | compile 'net.danlew:android.joda:2.9.5.1' 82 | 83 | compile 'com.google.dagger:dagger:2.9' 84 | annotationProcessor 'com.google.dagger:dagger-compiler:2.9' 85 | 86 | compile 'io.reactivex:rxandroid:1.2.1' 87 | compile 'io.reactivex:rxjava:1.2.7' 88 | 89 | compile 'org.greenrobot:eventbus:3.0.0' 90 | annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1' 91 | 92 | debugCompile 'com.facebook.stetho:stetho:1.4.2' 93 | debugCompile 'com.uphyca:stetho_realm:2.0.0' 94 | 95 | } 96 | -------------------------------------------------------------------------------- /app/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /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/kenji/Library/Android/sdk/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 | -dontwarn java.lang.invoke.* -------------------------------------------------------------------------------- /app/src/androidTest/java/com/star_zero/example/androidmvvm/MockApp.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm; 2 | 3 | import com.star_zero.example.androidmvvm.di.AppComponent; 4 | import com.star_zero.example.androidmvvm.di.DaggerMockAppComponent; 5 | import com.star_zero.example.androidmvvm.di.MockAppModule; 6 | 7 | public class MockApp extends App { 8 | 9 | @Override 10 | public AppComponent getAppComponent() { 11 | return DaggerMockAppComponent.builder() 12 | .mockAppModule(new MockAppModule()) 13 | .build(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/star_zero/example/androidmvvm/MockTestRunner.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.support.test.runner.AndroidJUnitRunner; 6 | 7 | public class MockTestRunner extends AndroidJUnitRunner { 8 | 9 | @Override 10 | public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { 11 | return super.newApplication(cl, MockApp.class.getName(), context); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/star_zero/example/androidmvvm/di/MockAppComponent.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.di; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.Component; 6 | 7 | @Component(modules = {MockAppModule.class}) 8 | @Singleton 9 | public interface MockAppComponent extends AppComponent { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/star_zero/example/androidmvvm/di/MockAppModule.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.di; 2 | 3 | import com.star_zero.example.androidmvvm.domain.task.TaskRepository; 4 | 5 | import javax.inject.Singleton; 6 | 7 | import dagger.Module; 8 | import dagger.Provides; 9 | 10 | @Module 11 | public class MockAppModule { 12 | 13 | @Provides 14 | @Singleton 15 | TaskRepository provideTaskRepository() { 16 | return new MockTaskRepository(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/star_zero/example/androidmvvm/di/MockTaskRepository.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.di; 2 | 3 | import com.star_zero.example.androidmvvm.domain.task.Task; 4 | import com.star_zero.example.androidmvvm.domain.task.TaskId; 5 | import com.star_zero.example.androidmvvm.domain.task.TaskRepository; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import rx.Observable; 11 | 12 | public class MockTaskRepository implements TaskRepository { 13 | 14 | @Override 15 | public TaskId generateTaskId() { 16 | return new TaskId("id"); 17 | } 18 | 19 | @Override 20 | public Observable save(Task task) { 21 | return Observable.just(true); 22 | } 23 | 24 | @Override 25 | public Observable update(Task task) { 26 | return Observable.just(true); 27 | } 28 | 29 | @Override 30 | public Observable delete(Task task) { 31 | return Observable.just(true); 32 | } 33 | 34 | @Override 35 | public Observable> getTasks() { 36 | List tasks = new ArrayList<>(); 37 | tasks.add(Task.createNewTask(new TaskId("id1"), "title1", "description1")); 38 | tasks.add(Task.createNewTask(new TaskId("id2"), "title2", "description2")); 39 | return Observable.just(tasks); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/star_zero/example/androidmvvm/matcher/InputTextLayoutMatcher.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.matcher; 2 | 3 | import android.content.res.Resources; 4 | import android.support.design.widget.TextInputLayout; 5 | import android.support.test.espresso.matcher.BoundedMatcher; 6 | import android.view.View; 7 | 8 | import org.hamcrest.Description; 9 | import org.hamcrest.Matcher; 10 | 11 | public class InputTextLayoutMatcher { 12 | 13 | public static Matcher isErrorEnabled(final boolean enabled) { 14 | return new BoundedMatcher(TextInputLayout.class) { 15 | @Override 16 | public void describeTo(Description description) { 17 | description.appendText("isErrorEnabled: " + enabled); 18 | } 19 | 20 | @Override 21 | public boolean matchesSafely(TextInputLayout view) { 22 | return enabled == view.isErrorEnabled(); 23 | } 24 | }; 25 | } 26 | 27 | public static Matcher withErrorText(final int resId) { 28 | return new BoundedMatcher(TextInputLayout.class) { 29 | @Override 30 | public void describeTo(Description description) { 31 | description.appendText("error resource id: " + resId); 32 | } 33 | 34 | @Override 35 | public boolean matchesSafely(TextInputLayout view) { 36 | String expect; 37 | try { 38 | expect = view.getResources().getString(resId); 39 | } catch (Resources.NotFoundException e) { 40 | return false; 41 | } 42 | 43 | String actual = view.getError().toString(); 44 | 45 | return expect.equals(actual); 46 | } 47 | }; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/star_zero/example/androidmvvm/presentation/add_edit_task/AddEditTaskActivityTest.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.presentation.add_edit_task; 2 | 3 | import android.content.Intent; 4 | import android.support.test.filters.LargeTest; 5 | import android.support.test.rule.ActivityTestRule; 6 | import android.support.test.runner.AndroidJUnit4; 7 | 8 | import com.star_zero.example.androidmvvm.R; 9 | import com.star_zero.example.androidmvvm.domain.task.Task; 10 | import com.star_zero.example.androidmvvm.domain.task.TaskId; 11 | 12 | import org.junit.Rule; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | 16 | import static android.support.test.espresso.Espresso.onView; 17 | import static android.support.test.espresso.action.ViewActions.click; 18 | import static android.support.test.espresso.action.ViewActions.typeText; 19 | import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; 20 | import static android.support.test.espresso.assertion.ViewAssertions.matches; 21 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 22 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 23 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 24 | import static com.star_zero.example.androidmvvm.matcher.InputTextLayoutMatcher.isErrorEnabled; 25 | import static com.star_zero.example.androidmvvm.matcher.InputTextLayoutMatcher.withErrorText; 26 | import static org.junit.Assert.assertTrue; 27 | 28 | @RunWith(AndroidJUnit4.class) 29 | @LargeTest 30 | public class AddEditTaskActivityTest { 31 | 32 | @Rule 33 | public ActivityTestRule activityRule = new ActivityTestRule<>(AddEditTaskActivity.class); 34 | 35 | @Test 36 | public void initViewNewTask() { 37 | onView(withId(R.id.edit_title)).check(matches(withText(""))); 38 | onView(withId(R.id.edit_description)).check(matches(withText(""))); 39 | onView(withId(R.id.menu_delete)).check(doesNotExist()); 40 | } 41 | 42 | @Test 43 | public void initViewUpdateTask() { 44 | Intent intent = new Intent(); 45 | intent.putExtra("task", Task.createNewTask(new TaskId("id"), "title", "description")); 46 | activityRule.launchActivity(intent); 47 | 48 | onView(withId(R.id.edit_title)).check(matches(withText("title"))); 49 | onView(withId(R.id.edit_description)).check(matches(withText("description"))); 50 | onView(withId(R.id.menu_delete)).check(matches(isDisplayed())); 51 | } 52 | 53 | @Test 54 | public void saveSuccess() { 55 | onView(withId(R.id.edit_title)).perform(typeText("title")); 56 | onView(withId(R.id.edit_description)).perform(typeText("description")); 57 | 58 | onView(withId(R.id.button_save)).perform(click()); 59 | 60 | assertTrue(activityRule.getActivity().isFinishing()); 61 | } 62 | 63 | @Test 64 | public void saveValidationError() { 65 | onView(withId(R.id.edit_title)).perform(typeText("")); 66 | onView(withId(R.id.edit_description)).perform(typeText("")); 67 | 68 | onView(withId(R.id.button_save)).perform(click()); 69 | 70 | onView(withId(R.id.input_layout_title)).check(matches(isErrorEnabled(true))); 71 | onView(withId(R.id.input_layout_title)).check(matches(withErrorText(R.string.validation_error_title_empty))); 72 | } 73 | 74 | @Test 75 | public void deleteSuccess() { 76 | Intent intent = new Intent(); 77 | intent.putExtra("task", Task.createNewTask(new TaskId("id"), "title", "description")); 78 | activityRule.launchActivity(intent); 79 | 80 | onView(withId(R.id.menu_delete)).perform(click()); 81 | 82 | assertTrue(activityRule.getActivity().isFinishing()); 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/star_zero/example/androidmvvm/presentation/tasks/TasksActivityTest.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.presentation.tasks; 2 | 3 | import android.support.test.espresso.contrib.RecyclerViewActions; 4 | import android.support.test.espresso.intent.rule.IntentsTestRule; 5 | import android.support.test.filters.LargeTest; 6 | import android.support.test.runner.AndroidJUnit4; 7 | 8 | import com.star_zero.example.androidmvvm.R; 9 | import com.star_zero.example.androidmvvm.domain.task.Task; 10 | import com.star_zero.example.androidmvvm.presentation.add_edit_task.AddEditTaskActivity; 11 | 12 | import org.junit.Rule; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | 16 | import static android.support.test.espresso.Espresso.onView; 17 | import static android.support.test.espresso.action.ViewActions.click; 18 | import static android.support.test.espresso.assertion.ViewAssertions.matches; 19 | import static android.support.test.espresso.intent.Intents.intended; 20 | import static android.support.test.espresso.intent.matcher.ComponentNameMatchers.hasClassName; 21 | import static android.support.test.espresso.intent.matcher.ComponentNameMatchers.hasMyPackageName; 22 | import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent; 23 | import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra; 24 | import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; 25 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 26 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 27 | import static org.hamcrest.Matchers.allOf; 28 | import static org.hamcrest.Matchers.instanceOf; 29 | import static org.hamcrest.Matchers.is; 30 | 31 | @RunWith(AndroidJUnit4.class) 32 | @LargeTest 33 | public class TasksActivityTest { 34 | 35 | @Rule 36 | public IntentsTestRule intentsRule = new IntentsTestRule<>(TasksActivity.class); 37 | 38 | @Test 39 | public void initView() { 40 | onView(withId(R.id.recycler_tasks)).check(matches(hasDescendant(withText("title1")))); 41 | onView(withId(R.id.recycler_tasks)).check(matches(hasDescendant(withText("title2")))); 42 | } 43 | 44 | @Test 45 | public void clickNewTask() { 46 | onView(withId(R.id.button_new_task)).perform(click()); 47 | 48 | intended(allOf( 49 | hasComponent(hasMyPackageName()), 50 | hasComponent(hasClassName(AddEditTaskActivity.class.getName())) 51 | )); 52 | } 53 | 54 | @Test 55 | public void clickItem() { 56 | onView(withId(R.id.recycler_tasks)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click())); 57 | 58 | intended(allOf( 59 | hasComponent(hasMyPackageName()), 60 | hasComponent(hasClassName(AddEditTaskActivity.class.getName())), 61 | hasExtra(is("task"), instanceOf(Task.class)) 62 | )); 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/debug/java/com/star_zero/example/androidmvvm/DebugApp.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm; 2 | 3 | import com.facebook.stetho.Stetho; 4 | import com.uphyca.stetho_realm.RealmInspectorModulesProvider; 5 | 6 | import timber.log.Timber; 7 | 8 | public class DebugApp extends App { 9 | 10 | @Override 11 | public void onCreate() { 12 | super.onCreate(); 13 | 14 | Timber.plant(new Timber.DebugTree()); 15 | 16 | Stetho.initialize( 17 | Stetho.newInitializerBuilder(this) 18 | .enableDumpapp(Stetho.defaultDumperPluginsProvider(this)) 19 | .enableWebKitInspector(RealmInspectorModulesProvider.builder(this).build()) 20 | .build() 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/App.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm; 2 | 3 | import android.app.Application; 4 | 5 | import com.star_zero.example.androidmvvm.di.AppComponent; 6 | import com.star_zero.example.androidmvvm.di.AppModule; 7 | import com.star_zero.example.androidmvvm.di.DaggerAppComponent; 8 | 9 | import net.danlew.android.joda.JodaTimeAndroid; 10 | 11 | import org.greenrobot.eventbus.EventBus; 12 | 13 | import io.realm.Realm; 14 | 15 | public class App extends Application { 16 | 17 | private AppComponent appComponent; 18 | 19 | @Override 20 | public void onCreate() { 21 | super.onCreate(); 22 | 23 | JodaTimeAndroid.init(this); 24 | 25 | Realm.init(this); 26 | 27 | EventBus.builder().addIndex(new EventBusIndex()).installDefaultEventBus(); 28 | 29 | appComponent = DaggerAppComponent.builder() 30 | .appModule(new AppModule()) 31 | .build(); 32 | 33 | } 34 | 35 | public AppComponent getAppComponent() { 36 | return appComponent; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/application/TaskService.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.application; 2 | 3 | import com.star_zero.example.androidmvvm.application.dto.TaskDTO; 4 | import com.star_zero.example.androidmvvm.domain.task.Task; 5 | import com.star_zero.example.androidmvvm.domain.task.TaskRepository; 6 | import com.star_zero.example.androidmvvm.domain.task.TaskValidator; 7 | 8 | import java.util.List; 9 | 10 | import javax.inject.Inject; 11 | 12 | import rx.Observable; 13 | import rx.Observer; 14 | import rx.android.schedulers.AndroidSchedulers; 15 | import rx.schedulers.Schedulers; 16 | import rx.subjects.PublishSubject; 17 | import rx.subscriptions.CompositeSubscription; 18 | import timber.log.Timber; 19 | 20 | public class TaskService { 21 | 22 | private TaskRepository taskRepository; 23 | 24 | private CompositeSubscription subscriptions = new CompositeSubscription(); 25 | 26 | private TaskValidator validator = new TaskValidator(); 27 | 28 | @Inject 29 | TaskService(TaskRepository taskRepository) { 30 | this.taskRepository = taskRepository; 31 | } 32 | 33 | public void getTasks() { 34 | subscriptions.add(taskRepository.getTasks() 35 | .subscribeOn(Schedulers.newThread()) 36 | .observeOn(AndroidSchedulers.mainThread()) 37 | .subscribe(new Observer>() { 38 | @Override 39 | public void onCompleted() { 40 | } 41 | 42 | @Override 43 | public void onError(Throwable e) { 44 | Timber.w(e); 45 | errorGetTasksSubject.onNext(null); 46 | } 47 | 48 | @Override 49 | public void onNext(List tasks) { 50 | tasksSubject.onNext(tasks); 51 | } 52 | }) 53 | ); 54 | } 55 | 56 | public void save(TaskDTO taskDTO) { 57 | taskDTO.clearValidationErrors(); 58 | 59 | if (!validator.validate(taskDTO.getTitle(), taskDTO.getDescription())) { 60 | validationErrorSubject.onNext(null); 61 | taskDTO.setValidationErrors(validator.getErrors()); 62 | return; 63 | } 64 | 65 | if (taskDTO.getTask() == null) { 66 | saveNewTask(taskDTO); 67 | } else { 68 | updateTask(taskDTO); 69 | } 70 | } 71 | 72 | private void saveNewTask(TaskDTO taskDTO) { 73 | 74 | Task task = Task.createNewTask(taskRepository.generateTaskId(), taskDTO.getTitle(), taskDTO.getDescription()); 75 | 76 | subscriptions.add(taskRepository.save(task) 77 | .subscribeOn(Schedulers.newThread()) 78 | .unsubscribeOn(AndroidSchedulers.mainThread()) 79 | .subscribe(new Observer() { 80 | @Override 81 | public void onCompleted() { 82 | } 83 | 84 | @Override 85 | public void onError(Throwable e) { 86 | Timber.w(e); 87 | errorSaveTaskSubject.onNext(null); 88 | } 89 | 90 | @Override 91 | public void onNext(Boolean result) { 92 | if (result) { 93 | successSaveTaskSubject.onNext(null); 94 | } else { 95 | errorSaveTaskSubject.onNext(null); 96 | } 97 | } 98 | }) 99 | ); 100 | } 101 | 102 | private void updateTask(TaskDTO taskDTO) { 103 | Task task = taskDTO.getTask(); 104 | task.update(taskDTO.getTitle(), taskDTO.getDescription()); 105 | 106 | subscriptions.add(taskRepository.update(task) 107 | .subscribe(new Observer() { 108 | @Override 109 | public void onCompleted() { 110 | } 111 | 112 | @Override 113 | public void onError(Throwable e) { 114 | Timber.w(e); 115 | errorSaveTaskSubject.onNext(null); 116 | } 117 | 118 | @Override 119 | public void onNext(Boolean result) { 120 | if (result) { 121 | successSaveTaskSubject.onNext(null); 122 | } else { 123 | errorSaveTaskSubject.onNext(null); 124 | } 125 | } 126 | }) 127 | ); 128 | 129 | } 130 | 131 | public void changeCompleteState(Task task, boolean completed) { 132 | if (completed) { 133 | task.completeTask(); 134 | } else { 135 | task.activateTask(); 136 | } 137 | 138 | subscriptions.add(taskRepository.update(task) 139 | .subscribe(new Observer() { 140 | @Override 141 | public void onCompleted() { 142 | } 143 | 144 | @Override 145 | public void onError(Throwable e) { 146 | Timber.w(e); 147 | errorChangeCompleteStateSubject.onNext(null); 148 | 149 | // rollback 150 | if (completed) { 151 | task.activateTask(); 152 | } else { 153 | task.completeTask(); 154 | } 155 | } 156 | 157 | @Override 158 | public void onNext(Boolean result) { 159 | } 160 | }) 161 | ); 162 | } 163 | 164 | public void deleteTask(Task task) { 165 | 166 | taskRepository.delete(task).subscribe(new Observer() { 167 | @Override 168 | public void onCompleted() { 169 | } 170 | 171 | @Override 172 | public void onError(Throwable e) { 173 | Timber.w(e); 174 | errorDeleteTaskSubject.onNext(null); 175 | } 176 | 177 | @Override 178 | public void onNext(Boolean result) { 179 | if (result) { 180 | successDeleteTaskSubject.onNext(null); 181 | } else { 182 | errorDeleteTaskSubject.onNext(null); 183 | } 184 | } 185 | }); 186 | } 187 | 188 | public void onDestroy() { 189 | subscriptions.clear(); 190 | } 191 | 192 | // ---------------------- 193 | // Notification 194 | // ---------------------- 195 | 196 | // getTasks 197 | private final PublishSubject> tasksSubject = PublishSubject.create(); 198 | public final Observable> tasks = tasksSubject.asObservable(); 199 | private final PublishSubject errorGetTasksSubject = PublishSubject.create(); 200 | public final Observable errorGetTasks = errorGetTasksSubject.asObservable(); 201 | 202 | // save 203 | private final PublishSubject successSaveTaskSubject = PublishSubject.create(); 204 | public final Observable successSaveTask = successSaveTaskSubject.asObservable(); 205 | private final PublishSubject errorSaveTaskSubject = PublishSubject.create(); 206 | public final Observable errorSaveTask = errorSaveTaskSubject.asObservable(); 207 | 208 | // validation 209 | private final PublishSubject validationErrorSubject = PublishSubject.create(); 210 | public final Observable validationError = validationErrorSubject.asObservable(); 211 | 212 | // completeTask, activateTask 213 | private final PublishSubject errorChangeCompleteStateSubject = PublishSubject.create(); 214 | public final Observable errorChangeCompleteState = errorChangeCompleteStateSubject.asObservable(); 215 | 216 | // delete 217 | private final PublishSubject successDeleteTaskSubject = PublishSubject.create(); 218 | public final Observable successDeleteTask = successDeleteTaskSubject.asObservable(); 219 | private final PublishSubject errorDeleteTaskSubject = PublishSubject.create(); 220 | public final Observable errorDeleteTask = errorDeleteTaskSubject.asObservable(); 221 | } 222 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/application/dto/TaskDTO.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.application.dto; 2 | 3 | import android.databinding.BaseObservable; 4 | import android.databinding.Bindable; 5 | import android.os.Parcel; 6 | import android.os.Parcelable; 7 | import android.support.annotation.StringRes; 8 | 9 | import com.android.databinding.library.baseAdapters.BR; 10 | import com.star_zero.example.androidmvvm.R; 11 | import com.star_zero.example.androidmvvm.domain.task.Task; 12 | import com.star_zero.example.androidmvvm.domain.task.TaskValidator; 13 | 14 | import java.util.List; 15 | 16 | public class TaskDTO extends BaseObservable implements Parcelable { 17 | 18 | private Task task; 19 | 20 | private String title; 21 | 22 | private String description; 23 | 24 | @StringRes 25 | private int titleError; 26 | 27 | @StringRes 28 | private int descriptionError; 29 | 30 | public TaskDTO() { 31 | } 32 | 33 | public Task getTask() { 34 | return task; 35 | } 36 | 37 | @Bindable 38 | public String getTitle() { 39 | return title; 40 | } 41 | 42 | public void setTitle(String title) { 43 | this.title = title; 44 | notifyPropertyChanged(BR.title); 45 | } 46 | 47 | @Bindable 48 | public String getDescription() { 49 | return description; 50 | } 51 | 52 | public void setDescription(String description) { 53 | this.description = description; 54 | notifyPropertyChanged(BR.description); 55 | } 56 | 57 | @Bindable 58 | @StringRes 59 | public int getTitleError() { 60 | return titleError; 61 | } 62 | 63 | public void setTitleError(@StringRes int titleError) { 64 | this.titleError = titleError; 65 | notifyPropertyChanged(BR.titleError); 66 | } 67 | 68 | @Bindable 69 | @StringRes 70 | public int getDescriptionError() { 71 | return descriptionError; 72 | } 73 | 74 | public void setDescriptionError(@StringRes int descriptionError) { 75 | this.descriptionError = descriptionError; 76 | notifyPropertyChanged(BR.descriptionError); 77 | } 78 | 79 | public static TaskDTO createFromTask(Task task) { 80 | TaskDTO dto = new TaskDTO(); 81 | dto.task = task; 82 | dto.setTitle(task.getTitle()); 83 | dto.setDescription(task.getDescription()); 84 | return dto; 85 | } 86 | 87 | public void clearValidationErrors() { 88 | setTitleError(0); 89 | setDescriptionError(0); 90 | } 91 | 92 | public void setValidationErrors(List errors) { 93 | for (TaskValidator.ErrorType errorType : errors) { 94 | switch (errorType) { 95 | case TITLE_EMPTY: 96 | setTitleError(R.string.validation_error_title_empty); 97 | break; 98 | case TITLE_TOO_LONG: 99 | setTitleError(R.string.validation_error_title_too_long); 100 | break; 101 | case DESCRIPTION_TOO_LONG: 102 | setDescriptionError(R.string.validation_error_description_too_long); 103 | break; 104 | } 105 | } 106 | } 107 | 108 | // ---------------------- 109 | // Parcelable 110 | // ---------------------- 111 | 112 | @Override 113 | public int describeContents() { 114 | return 0; 115 | } 116 | 117 | @Override 118 | public void writeToParcel(Parcel dest, int flags) { 119 | dest.writeParcelable(this.task, flags); 120 | dest.writeString(this.title); 121 | dest.writeString(this.description); 122 | } 123 | 124 | private TaskDTO(Parcel in) { 125 | this.task = in.readParcelable(Task.class.getClassLoader()); 126 | this.title = in.readString(); 127 | this.description = in.readString(); 128 | } 129 | 130 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 131 | @Override 132 | public TaskDTO createFromParcel(Parcel source) { 133 | return new TaskDTO(source); 134 | } 135 | 136 | @Override 137 | public TaskDTO[] newArray(int size) { 138 | return new TaskDTO[size]; 139 | } 140 | }; 141 | } 142 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/di/AppComponent.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.di; 2 | 3 | import com.star_zero.example.androidmvvm.presentation.add_edit_task.AddEditTaskActivity; 4 | import com.star_zero.example.androidmvvm.presentation.tasks.TasksActivity; 5 | 6 | import javax.inject.Singleton; 7 | 8 | import dagger.Component; 9 | 10 | @Component(modules = {AppModule.class}) 11 | @Singleton 12 | public interface AppComponent { 13 | 14 | void inject(TasksActivity activity); 15 | 16 | void inject(AddEditTaskActivity activity); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/di/AppModule.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.di; 2 | 3 | import com.star_zero.example.androidmvvm.domain.task.TaskRepository; 4 | import com.star_zero.example.androidmvvm.infrastructure.task.RealmTaskRepository; 5 | 6 | import javax.inject.Singleton; 7 | 8 | import dagger.Module; 9 | import dagger.Provides; 10 | 11 | @Module 12 | public class AppModule { 13 | 14 | @Provides 15 | @Singleton 16 | TaskRepository provideTaskRepository() { 17 | return new RealmTaskRepository(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/domain/Entity.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.domain; 2 | 3 | import android.databinding.BaseObservable; 4 | import android.support.annotation.NonNull; 5 | 6 | public abstract class Entity extends BaseObservable { 7 | 8 | @NonNull 9 | public final T id; 10 | 11 | protected Entity(@NonNull T id) { 12 | this.id = id; 13 | } 14 | 15 | @Override 16 | public boolean equals(Object o) { 17 | if (this == o) return true; 18 | if (o == null || getClass() != o.getClass()) return false; 19 | 20 | Entity entity = (Entity) o; 21 | 22 | return id.equals(entity.id); 23 | } 24 | 25 | @Override 26 | public int hashCode() { 27 | return id.hashCode(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/domain/Identifier.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.domain; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | public abstract class Identifier { 6 | 7 | @NonNull 8 | public final String value; 9 | 10 | public Identifier(@NonNull String value) { 11 | this.value = value; 12 | } 13 | 14 | @Override 15 | public boolean equals(Object o) { 16 | if (this == o) return true; 17 | if (o == null || getClass() != o.getClass()) return false; 18 | 19 | Identifier that = (Identifier) o; 20 | 21 | return value.equals(that.value); 22 | } 23 | 24 | @Override 25 | public int hashCode() { 26 | return value.hashCode(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/domain/task/Task.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.domain.task; 2 | 3 | import android.databinding.Bindable; 4 | import android.os.Parcel; 5 | import android.os.Parcelable; 6 | 7 | import com.android.databinding.library.baseAdapters.BR; 8 | import com.star_zero.example.androidmvvm.domain.Entity; 9 | 10 | import org.joda.time.DateTime; 11 | 12 | public class Task extends Entity implements Parcelable { 13 | 14 | private String title; 15 | 16 | private String description; 17 | 18 | private boolean completed; 19 | 20 | private DateTime createdAt; 21 | 22 | protected Task(TaskId taskId) { 23 | super(taskId); 24 | } 25 | 26 | // ---------------------- 27 | // Factory 28 | // ---------------------- 29 | 30 | /** 31 | * @deprecated Use from persistent only. 32 | */ 33 | public static Task createFromPersistent(TaskId taskId, String title, String description, boolean completed, DateTime createdAt) { 34 | Task task = new Task(taskId); 35 | task.setTitle(title); 36 | task.setDescription(description); 37 | task.setCompleted(completed); 38 | task.setCreatedAt(createdAt); 39 | return task; 40 | } 41 | 42 | public static Task createNewTask(TaskId taskId, String title, String description) { 43 | Task task = new Task(taskId); 44 | task.setTitle(title); 45 | task.setDescription(description); 46 | task.setCompleted(false); 47 | task.setCreatedAt(DateTime.now()); 48 | return task; 49 | } 50 | 51 | // ---------------------- 52 | // Operation 53 | // ---------------------- 54 | 55 | public void completeTask() { 56 | setCompleted(true); 57 | } 58 | 59 | public void activateTask() { 60 | setCompleted(false); 61 | } 62 | 63 | public void update(String title, String description) { 64 | setTitle(title); 65 | setDescription(description); 66 | } 67 | 68 | // ---------------------- 69 | // Getter/Setter 70 | // ---------------------- 71 | 72 | @Bindable 73 | public String getTitle() { 74 | return title; 75 | } 76 | 77 | private void setTitle(String title) { 78 | this.title = title; 79 | notifyPropertyChanged(BR.title); 80 | } 81 | 82 | @Bindable 83 | public String getDescription() { 84 | return description; 85 | } 86 | 87 | private void setDescription(String description) { 88 | this.description = description; 89 | notifyPropertyChanged(BR.description); 90 | } 91 | 92 | @Bindable 93 | public boolean isCompleted() { 94 | return completed; 95 | } 96 | 97 | private void setCompleted(boolean completed) { 98 | this.completed = completed; 99 | notifyPropertyChanged(BR.completed); 100 | } 101 | 102 | @Bindable 103 | public DateTime getCreatedAt() { 104 | return createdAt; 105 | } 106 | 107 | private void setCreatedAt(DateTime createdAt) { 108 | this.createdAt = createdAt; 109 | notifyPropertyChanged(BR.createdAt); 110 | } 111 | 112 | // ---------------------- 113 | // Parcelable 114 | // ---------------------- 115 | 116 | @Override 117 | public int describeContents() { 118 | return 0; 119 | } 120 | 121 | @Override 122 | public void writeToParcel(Parcel dest, int flags) { 123 | dest.writeParcelable(id, flags); 124 | dest.writeString(this.title); 125 | dest.writeString(this.description); 126 | dest.writeByte(this.completed ? (byte) 1 : (byte) 0); 127 | dest.writeSerializable(this.createdAt); 128 | } 129 | 130 | protected Task(Parcel in) { 131 | this((TaskId) in.readParcelable(TaskId.class.getClassLoader())); 132 | this.title = in.readString(); 133 | this.description = in.readString(); 134 | this.completed = in.readByte() != 0; 135 | this.createdAt = (DateTime) in.readSerializable(); 136 | } 137 | 138 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 139 | @Override 140 | public Task createFromParcel(Parcel source) { 141 | return new Task(source); 142 | } 143 | 144 | @Override 145 | public Task[] newArray(int size) { 146 | return new Task[size]; 147 | } 148 | }; 149 | } 150 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/domain/task/TaskId.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.domain.task; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import android.support.annotation.NonNull; 6 | 7 | import com.star_zero.example.androidmvvm.domain.Identifier; 8 | 9 | public class TaskId extends Identifier implements Parcelable { 10 | 11 | public TaskId(@NonNull String value) { 12 | super(value); 13 | } 14 | 15 | @Override 16 | public int describeContents() { 17 | return 0; 18 | } 19 | 20 | @Override 21 | public void writeToParcel(Parcel dest, int flags) { 22 | dest.writeString(this.value); 23 | } 24 | 25 | private TaskId(Parcel in) { 26 | super(in.readString()); 27 | } 28 | 29 | public static final Creator CREATOR = new Creator() { 30 | @Override 31 | public TaskId createFromParcel(Parcel source) { 32 | return new TaskId(source); 33 | } 34 | 35 | @Override 36 | public TaskId[] newArray(int size) { 37 | return new TaskId[size]; 38 | } 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/domain/task/TaskRepository.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.domain.task; 2 | 3 | import java.util.List; 4 | 5 | import rx.Observable; 6 | 7 | public interface TaskRepository { 8 | 9 | TaskId generateTaskId(); 10 | 11 | Observable save(Task task); 12 | 13 | Observable update(Task task); 14 | 15 | Observable delete(Task task); 16 | 17 | Observable> getTasks(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/domain/task/TaskValidator.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.domain.task; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class TaskValidator { 7 | 8 | public enum ErrorType { 9 | TITLE_EMPTY, 10 | TITLE_TOO_LONG, 11 | DESCRIPTION_TOO_LONG 12 | } 13 | 14 | private List errors; 15 | 16 | public boolean validate(String title, String description) { 17 | errors = new ArrayList<>(); 18 | 19 | // title 20 | if (title == null || title.length() == 0) { 21 | errors.add(ErrorType.TITLE_EMPTY); 22 | } else if (title.length() > 32) { 23 | errors.add(ErrorType.TITLE_TOO_LONG); 24 | } 25 | 26 | // description 27 | if (description != null && description.length() > 512) { 28 | errors.add(ErrorType.DESCRIPTION_TOO_LONG); 29 | } 30 | 31 | return errors.size() == 0; 32 | } 33 | 34 | public List getErrors() { 35 | return errors; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/infrastructure/task/RealmTaskRepository.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.infrastructure.task; 2 | 3 | import com.star_zero.example.androidmvvm.domain.task.Task; 4 | import com.star_zero.example.androidmvvm.domain.task.TaskId; 5 | import com.star_zero.example.androidmvvm.domain.task.TaskRepository; 6 | 7 | import org.joda.time.DateTime; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.UUID; 12 | 13 | import io.realm.Realm; 14 | import io.realm.RealmResults; 15 | import rx.Emitter; 16 | import rx.Observable; 17 | 18 | public class RealmTaskRepository implements TaskRepository { 19 | 20 | @Override 21 | public TaskId generateTaskId() { 22 | return new TaskId(UUID.randomUUID().toString()); 23 | } 24 | 25 | @Override 26 | public Observable save(Task task) { 27 | return Observable.create(emitter -> { 28 | try (Realm realm = Realm.getDefaultInstance()) { 29 | realm.beginTransaction(); 30 | 31 | TaskTable taskTable = realm.createObject(TaskTable.class); 32 | 33 | taskTable.setId(task.id.value); 34 | taskTable.setTitle(task.getTitle()); 35 | taskTable.setDescription(task.getDescription()); 36 | taskTable.setCompleted(task.isCompleted()); 37 | taskTable.setCreatedAt(task.getCreatedAt().getMillis()); 38 | 39 | realm.commitTransaction(); 40 | 41 | emitter.onNext(true); 42 | emitter.onCompleted(); 43 | 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | emitter.onError(e); 47 | } 48 | }, Emitter.BackpressureMode.NONE); 49 | } 50 | 51 | @Override 52 | public Observable update(Task task) { 53 | return Observable.create(emitter -> { 54 | try (Realm realm = Realm.getDefaultInstance()) { 55 | realm.beginTransaction(); 56 | 57 | TaskTable taskTable = realm.where(TaskTable.class).equalTo("id", task.id.value).findFirst(); 58 | 59 | taskTable.setTitle(task.getTitle()); 60 | taskTable.setDescription(task.getDescription()); 61 | taskTable.setCompleted(task.isCompleted()); 62 | taskTable.setCreatedAt(task.getCreatedAt().getMillis()); 63 | 64 | realm.commitTransaction(); 65 | 66 | emitter.onNext(true); 67 | emitter.onCompleted(); 68 | 69 | } catch (Exception e) { 70 | e.printStackTrace(); 71 | emitter.onError(e); 72 | } 73 | }, Emitter.BackpressureMode.NONE); 74 | } 75 | 76 | @Override 77 | public Observable delete(Task task) { 78 | return Observable.create(emitter -> { 79 | try (Realm realm = Realm.getDefaultInstance()) { 80 | realm.beginTransaction(); 81 | 82 | TaskTable taskTable = realm.where(TaskTable.class).equalTo("id", task.id.value).findFirst(); 83 | 84 | taskTable.deleteFromRealm(); 85 | 86 | realm.commitTransaction(); 87 | 88 | emitter.onNext(true); 89 | emitter.onCompleted(); 90 | 91 | } catch (Exception e) { 92 | e.printStackTrace(); 93 | emitter.onError(e); 94 | } 95 | }, Emitter.BackpressureMode.NONE); 96 | } 97 | 98 | @Override 99 | public Observable> getTasks() { 100 | return Observable.create(emitter -> { 101 | try (Realm realm = Realm.getDefaultInstance()) { 102 | List tasks = new ArrayList<>(); 103 | 104 | RealmResults taskTables = realm.where(TaskTable.class).findAll().sort("createdAt"); 105 | 106 | for (TaskTable taskTable : taskTables) { 107 | @SuppressWarnings("deprecation") 108 | Task task = Task.createFromPersistent( 109 | new TaskId(taskTable.getId()), 110 | taskTable.getTitle(), 111 | taskTable.getDescription(), 112 | taskTable.isCompleted(), 113 | new DateTime(taskTable.getCreatedAt())); 114 | tasks.add(task); 115 | } 116 | 117 | emitter.onNext(tasks); 118 | emitter.onCompleted(); 119 | } catch (Exception e) { 120 | e.printStackTrace(); 121 | emitter.onError(e); 122 | } 123 | }, Emitter.BackpressureMode.NONE); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/infrastructure/task/TaskTable.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.infrastructure.task; 2 | 3 | import io.realm.RealmObject; 4 | 5 | public class TaskTable extends RealmObject { 6 | 7 | private String id; 8 | 9 | private String title; 10 | 11 | private String description; 12 | 13 | private boolean completed; 14 | 15 | private long createdAt; 16 | 17 | public String getId() { 18 | return id; 19 | } 20 | 21 | public void setId(String id) { 22 | this.id = id; 23 | } 24 | 25 | public String getTitle() { 26 | return title; 27 | } 28 | 29 | public void setTitle(String title) { 30 | this.title = title; 31 | } 32 | 33 | public String getDescription() { 34 | return description; 35 | } 36 | 37 | public void setDescription(String description) { 38 | this.description = description; 39 | } 40 | 41 | public boolean isCompleted() { 42 | return completed; 43 | } 44 | 45 | public void setCompleted(boolean completed) { 46 | this.completed = completed; 47 | } 48 | 49 | public long getCreatedAt() { 50 | return createdAt; 51 | } 52 | 53 | public void setCreatedAt(long createdAt) { 54 | this.createdAt = createdAt; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/presentation/add_edit_task/AddEditTaskActivity.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.presentation.add_edit_task; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.databinding.DataBindingUtil; 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.design.widget.Snackbar; 9 | import android.view.Menu; 10 | import android.view.MenuInflater; 11 | import android.view.MenuItem; 12 | 13 | import com.star_zero.example.androidmvvm.R; 14 | import com.star_zero.example.androidmvvm.databinding.ActivityAddEditTaskBinding; 15 | import com.star_zero.example.androidmvvm.domain.task.Task; 16 | import com.star_zero.example.androidmvvm.presentation.shared.view.BaseActivity; 17 | 18 | import javax.inject.Inject; 19 | 20 | import rx.subscriptions.CompositeSubscription; 21 | 22 | public class AddEditTaskActivity extends BaseActivity { 23 | 24 | private static final String ARGS_KEY_TASK = "task"; 25 | 26 | private ActivityAddEditTaskBinding binding; 27 | 28 | private CompositeSubscription subscriptions = new CompositeSubscription(); 29 | 30 | @Inject 31 | AddEditTaskViewModel viewModel; 32 | 33 | public static Intent createIntent(Context context, Task task) { 34 | Intent intent = new Intent(context, AddEditTaskActivity.class); 35 | intent.putExtra(ARGS_KEY_TASK, task); 36 | return intent; 37 | } 38 | 39 | @Override 40 | protected void onCreate(@Nullable Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | binding = DataBindingUtil.setContentView(this, R.layout.activity_add_edit_task); 43 | 44 | getAppComponent().inject(this); 45 | binding.setViewModel(viewModel); 46 | 47 | subscribe(); 48 | viewModel.onCreate(); 49 | 50 | setSupportActionBar(binding.toolbar); 51 | assert getSupportActionBar() != null; 52 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 53 | 54 | Intent intent = getIntent(); 55 | if (intent.hasExtra(ARGS_KEY_TASK)) { 56 | viewModel.setTask(intent.getParcelableExtra(ARGS_KEY_TASK)); 57 | } 58 | 59 | viewModel.restoreState(savedInstanceState); 60 | } 61 | 62 | @Override 63 | protected void onDestroy() { 64 | viewModel.onDestroy(); 65 | subscriptions.clear(); 66 | super.onDestroy(); 67 | } 68 | 69 | @Override 70 | public boolean onCreateOptionsMenu(Menu menu) { 71 | MenuInflater inflater = getMenuInflater(); 72 | inflater.inflate(R.menu.menu_add_edit_task, menu); 73 | menu.findItem(R.id.menu_delete).setVisible(viewModel.isSaved()); 74 | return super.onCreateOptionsMenu(menu); 75 | } 76 | 77 | @Override 78 | public boolean onOptionsItemSelected(MenuItem item) { 79 | switch (item.getItemId()) { 80 | case android.R.id.home: 81 | finish(); 82 | return true; 83 | 84 | case R.id.menu_delete: 85 | viewModel.deleteTask(); 86 | return true; 87 | } 88 | return super.onOptionsItemSelected(item); 89 | } 90 | 91 | @Override 92 | protected void onSaveInstanceState(Bundle outState) { 93 | viewModel.saveState(outState); 94 | super.onSaveInstanceState(outState); 95 | } 96 | 97 | // ---------------------- 98 | // Subscribe 99 | // ---------------------- 100 | 101 | private void subscribe() { 102 | subscriptions.add(viewModel.successSaveTask.subscribe(aVoid -> { 103 | finish(); 104 | })); 105 | 106 | subscriptions.add(viewModel.successDeleteTask.subscribe(aVoid -> { 107 | finish(); 108 | })); 109 | 110 | subscriptions.add(viewModel.errorMessage.subscribe(resId -> { 111 | Snackbar.make(binding.getRoot(), resId, Snackbar.LENGTH_LONG).show(); 112 | })); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/presentation/add_edit_task/AddEditTaskViewModel.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.presentation.add_edit_task; 2 | 3 | import android.databinding.ObservableField; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | 7 | import com.star_zero.example.androidmvvm.R; 8 | import com.star_zero.example.androidmvvm.application.TaskService; 9 | import com.star_zero.example.androidmvvm.application.dto.TaskDTO; 10 | import com.star_zero.example.androidmvvm.domain.task.Task; 11 | 12 | import javax.inject.Inject; 13 | 14 | import rx.Observable; 15 | import rx.subjects.PublishSubject; 16 | import rx.subscriptions.CompositeSubscription; 17 | 18 | public class AddEditTaskViewModel { 19 | 20 | private static final String STATE_KEY_TASK = "task"; 21 | 22 | public final ObservableField taskDTO = new ObservableField<>(); 23 | 24 | private TaskService taskService; 25 | 26 | private CompositeSubscription subscriptions = new CompositeSubscription(); 27 | 28 | @Inject 29 | AddEditTaskViewModel(TaskService taskService) { 30 | this.taskService = taskService; 31 | this.taskDTO.set(new TaskDTO()); 32 | } 33 | 34 | void onCreate() { 35 | subscribe(); 36 | } 37 | 38 | void onDestroy() { 39 | taskService.onDestroy(); 40 | subscriptions.clear(); 41 | } 42 | 43 | void restoreState(Bundle savedInstanceState) { 44 | if (savedInstanceState == null) return; 45 | taskDTO.set(savedInstanceState.getParcelable(STATE_KEY_TASK)); 46 | } 47 | 48 | void saveState(Bundle outState) { 49 | outState.putParcelable(STATE_KEY_TASK, taskDTO.get()); 50 | } 51 | 52 | // ---------------------- 53 | // Property 54 | // ---------------------- 55 | 56 | void setTask(Task task) { 57 | this.taskDTO.set(TaskDTO.createFromTask(task)); 58 | } 59 | 60 | boolean isSaved() { 61 | return taskDTO.get().getTask() != null; 62 | } 63 | 64 | // ---------------------- 65 | // Event 66 | // ---------------------- 67 | 68 | public void onClickSave(View view) { 69 | saveTask(); 70 | } 71 | 72 | // ---------------------- 73 | // operation 74 | // ---------------------- 75 | 76 | private void saveTask() { 77 | taskService.save(taskDTO.get()); 78 | } 79 | 80 | void deleteTask() { 81 | taskService.deleteTask(taskDTO.get().getTask()); 82 | } 83 | 84 | // ---------------------- 85 | // Subscribe 86 | // ---------------------- 87 | 88 | private void subscribe() { 89 | // save 90 | subscriptions.add(taskService.validationError.subscribe(aVoid -> { 91 | errorMessageSubject.onNext(R.string.error_validation); 92 | })); 93 | subscriptions.add(taskService.successSaveTask.subscribe(successSaveTaskSubject::onNext)); 94 | subscriptions.add(taskService.errorSaveTask.subscribe(aVoid -> { 95 | errorMessageSubject.onNext(R.string.error_save_task); 96 | })); 97 | 98 | // delete 99 | subscriptions.add(taskService.successDeleteTask.subscribe(successDeleteTaskSubject::onNext)); 100 | subscriptions.add(taskService.errorDeleteTask.subscribe(aVoid -> { 101 | errorMessageSubject.onNext(R.string.error_delete_task); 102 | })); 103 | } 104 | 105 | // ---------------------- 106 | // Notification 107 | // ---------------------- 108 | 109 | private final PublishSubject errorMessageSubject = PublishSubject.create(); 110 | final Observable errorMessage = errorMessageSubject.asObservable(); 111 | 112 | private final PublishSubject successSaveTaskSubject = PublishSubject.create(); 113 | final Observable successSaveTask = successSaveTaskSubject.asObservable(); 114 | 115 | private final PublishSubject successDeleteTaskSubject = PublishSubject.create(); 116 | final Observable successDeleteTask = successDeleteTaskSubject.asObservable(); 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/presentation/shared/binding/TextInputLayoutBinding.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.presentation.shared.binding; 2 | 3 | import android.databinding.BindingAdapter; 4 | import android.support.design.widget.TextInputLayout; 5 | 6 | public class TextInputLayoutBinding { 7 | 8 | @BindingAdapter({"errorMessage"}) 9 | public static void setErrorMessage(TextInputLayout view, int messageId) { 10 | if (messageId == 0) { 11 | view.setErrorEnabled(false); 12 | } else { 13 | view.setErrorEnabled(true); 14 | view.setError(view.getContext().getString(messageId)); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/presentation/shared/view/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.presentation.shared.view; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | 5 | import com.star_zero.example.androidmvvm.App; 6 | import com.star_zero.example.androidmvvm.di.AppComponent; 7 | 8 | public abstract class BaseActivity extends AppCompatActivity { 9 | 10 | protected AppComponent getAppComponent() { 11 | return ((App) getApplication()).getAppComponent(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/presentation/tasks/TasksActivity.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.presentation.tasks; 2 | 3 | import android.content.Intent; 4 | import android.databinding.DataBindingUtil; 5 | import android.os.Bundle; 6 | import android.support.design.widget.Snackbar; 7 | import android.support.v7.widget.DividerItemDecoration; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | 10 | import com.star_zero.example.androidmvvm.R; 11 | import com.star_zero.example.androidmvvm.databinding.ActivityTasksBinding; 12 | import com.star_zero.example.androidmvvm.presentation.add_edit_task.AddEditTaskActivity; 13 | import com.star_zero.example.androidmvvm.presentation.shared.view.BaseActivity; 14 | import com.star_zero.example.androidmvvm.presentation.tasks.adapter.ItemTaskViewModel; 15 | 16 | import org.greenrobot.eventbus.EventBus; 17 | import org.greenrobot.eventbus.Subscribe; 18 | import org.greenrobot.eventbus.ThreadMode; 19 | 20 | import javax.inject.Inject; 21 | 22 | import rx.subscriptions.CompositeSubscription; 23 | 24 | public class TasksActivity extends BaseActivity { 25 | 26 | private ActivityTasksBinding binding; 27 | 28 | private CompositeSubscription subscriptions = new CompositeSubscription(); 29 | 30 | @Inject 31 | TasksViewModel viewModel; 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | binding = DataBindingUtil.setContentView(this, R.layout.activity_tasks); 37 | 38 | getAppComponent().inject(this); 39 | binding.setViewModel(viewModel); 40 | 41 | subscribe(); 42 | viewModel.onCreate(); 43 | 44 | setSupportActionBar(binding.toolbar); 45 | 46 | binding.recyclerTasks.setLayoutManager(new LinearLayoutManager(this)); 47 | binding.recyclerTasks.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); 48 | } 49 | 50 | @Override 51 | protected void onStart() { 52 | super.onStart(); 53 | EventBus.getDefault().register(this); 54 | viewModel.onStart(); 55 | } 56 | 57 | @Override 58 | protected void onStop() { 59 | EventBus.getDefault().unregister(this); 60 | super.onStop(); 61 | } 62 | 63 | @Override 64 | protected void onDestroy() { 65 | viewModel.onDestroy(); 66 | subscriptions.clear(); 67 | super.onDestroy(); 68 | } 69 | 70 | // ---------------------- 71 | // EventBus subscribe (ItemTaskViewModel) 72 | // ---------------------- 73 | 74 | @Subscribe(threadMode = ThreadMode.MAIN) 75 | public void onClickTaskItem(ItemTaskViewModel.ClickTaskItemEvent event) { 76 | Intent intent = AddEditTaskActivity.createIntent(this, event.task); 77 | startActivity(intent); 78 | } 79 | 80 | @Subscribe(threadMode = ThreadMode.MAIN) 81 | public void onChangeCompleteState(ItemTaskViewModel.ChangeCompleteStateEvent event) { 82 | viewModel.changeCompleteState(event.task, event.completed); 83 | } 84 | 85 | // ---------------------- 86 | // Subscribe 87 | // ---------------------- 88 | 89 | private void subscribe() { 90 | subscriptions.add(viewModel.clickNewTask.subscribe(aVoid -> { 91 | Intent intent = new Intent(this, AddEditTaskActivity.class); 92 | startActivity(intent); 93 | })); 94 | 95 | subscriptions.add(viewModel.errorMessage.subscribe(resId -> { 96 | Snackbar.make(binding.getRoot(), resId, Snackbar.LENGTH_LONG).show(); 97 | })); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/presentation/tasks/TasksViewModel.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.presentation.tasks; 2 | 3 | import android.view.View; 4 | 5 | import com.star_zero.example.androidmvvm.R; 6 | import com.star_zero.example.androidmvvm.application.TaskService; 7 | import com.star_zero.example.androidmvvm.domain.task.Task; 8 | import com.star_zero.example.androidmvvm.presentation.tasks.adapter.TasksAdapter; 9 | 10 | import javax.inject.Inject; 11 | 12 | import rx.Observable; 13 | import rx.subjects.PublishSubject; 14 | import rx.subscriptions.CompositeSubscription; 15 | 16 | public class TasksViewModel { 17 | 18 | private TaskService taskService; 19 | 20 | public final TasksAdapter adapter = new TasksAdapter(); 21 | 22 | private CompositeSubscription subscriptions = new CompositeSubscription(); 23 | 24 | @Inject 25 | TasksViewModel(TaskService taskService) { 26 | this.taskService = taskService; 27 | } 28 | 29 | void onCreate() { 30 | subscribe(); 31 | } 32 | 33 | void onStart() { 34 | getTasks(); 35 | } 36 | 37 | void onDestroy() { 38 | taskService.onDestroy(); 39 | subscriptions.clear(); 40 | } 41 | 42 | // ---------------------- 43 | // Event 44 | // ---------------------- 45 | 46 | public void onClickNewTask(View view) { 47 | clickNewTaskSubject.onNext(null); 48 | } 49 | 50 | // ---------------------- 51 | // Operation 52 | // ---------------------- 53 | 54 | private void getTasks() { 55 | taskService.getTasks(); 56 | } 57 | 58 | void changeCompleteState(Task task, boolean completed) { 59 | taskService.changeCompleteState(task, completed); 60 | } 61 | 62 | // ---------------------- 63 | // Subscribe 64 | // ---------------------- 65 | 66 | private void subscribe() { 67 | subscriptions.add(taskService.tasks.subscribe(adapter::setTasks)); 68 | subscriptions.add(taskService.errorGetTasks.subscribe(aVoid -> { 69 | errorMessageSubject.onNext(R.string.error_get_task); 70 | })); 71 | subscriptions.add(taskService.errorChangeCompleteState.subscribe(aVoid -> { 72 | errorMessageSubject.onNext(R.string.error_change_state); 73 | })); 74 | } 75 | 76 | // ---------------------- 77 | // Notification 78 | // ---------------------- 79 | 80 | private final PublishSubject clickNewTaskSubject = PublishSubject.create(); 81 | final Observable clickNewTask = clickNewTaskSubject.asObservable(); 82 | 83 | private final PublishSubject errorMessageSubject = PublishSubject.create(); 84 | final Observable errorMessage = errorMessageSubject.asObservable(); 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/presentation/tasks/adapter/ItemTaskViewModel.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.presentation.tasks.adapter; 2 | 3 | import android.databinding.ObservableField; 4 | import android.view.View; 5 | import android.widget.CompoundButton; 6 | 7 | import com.star_zero.example.androidmvvm.domain.task.Task; 8 | 9 | import org.greenrobot.eventbus.EventBus; 10 | 11 | public class ItemTaskViewModel { 12 | 13 | public final ObservableField task = new ObservableField<>(); 14 | 15 | void setTask(Task task) { 16 | this.task.set(task); 17 | } 18 | 19 | public void clickItem(View view) { 20 | EventBus.getDefault().post(new ClickTaskItemEvent(task.get())); 21 | } 22 | 23 | public void changeCheckComplete(CompoundButton view, boolean isChecked) { 24 | if (isChecked != task.get().isCompleted()) { 25 | EventBus.getDefault().post(new ChangeCompleteStateEvent(task.get(), isChecked)); 26 | } 27 | } 28 | 29 | // ---------------------- 30 | // EventBus event class 31 | // ---------------------- 32 | 33 | public static class ClickTaskItemEvent { 34 | public final Task task; 35 | 36 | ClickTaskItemEvent(Task task) { 37 | this.task = task; 38 | } 39 | } 40 | 41 | public static class ChangeCompleteStateEvent { 42 | public final Task task; 43 | public final boolean completed; 44 | 45 | ChangeCompleteStateEvent(Task task, boolean completed) { 46 | this.task = task; 47 | this.completed = completed; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/star_zero/example/androidmvvm/presentation/tasks/adapter/TasksAdapter.java: -------------------------------------------------------------------------------- 1 | package com.star_zero.example.androidmvvm.presentation.tasks.adapter; 2 | 3 | import android.content.Context; 4 | import android.databinding.DataBindingUtil; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.star_zero.example.androidmvvm.R; 11 | import com.star_zero.example.androidmvvm.databinding.ItemTaskBinding; 12 | import com.star_zero.example.androidmvvm.domain.task.Task; 13 | 14 | import java.util.List; 15 | 16 | public class TasksAdapter extends RecyclerView.Adapter { 17 | 18 | private List tasks; 19 | 20 | @Override 21 | public TasksAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 22 | Context context = parent.getContext(); 23 | LayoutInflater inflater = LayoutInflater.from(context); 24 | View view = inflater.inflate(R.layout.item_task, parent, false); 25 | return new ViewHolder(view); 26 | } 27 | 28 | @Override 29 | public void onBindViewHolder(TasksAdapter.ViewHolder holder, int position) { 30 | holder.setTask(tasks.get(position)); 31 | } 32 | 33 | @Override 34 | public int getItemCount() { 35 | if (tasks == null) return 0; 36 | return tasks.size(); 37 | } 38 | 39 | public void setTasks(List tasks) { 40 | this.tasks = tasks; 41 | notifyDataSetChanged(); 42 | } 43 | 44 | static class ViewHolder extends RecyclerView.ViewHolder { 45 | 46 | private ItemTaskBinding binding; 47 | 48 | private ItemTaskViewModel viewModel; 49 | 50 | ViewHolder(View itemView) { 51 | super(itemView); 52 | 53 | binding = DataBindingUtil.bind(itemView); 54 | 55 | viewModel = new ItemTaskViewModel(); 56 | binding.setViewModel(viewModel); 57 | } 58 | 59 | void setTask(Task task) { 60 | viewModel.setTask(task); 61 | binding.executePendingBindings(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_done.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_add_edit_task.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 17 | 18 | 22 | 23 | 27 | 28 | 34 | 35 | 36 | 37 | 42 | 43 | 51 | 52 | 57 | 58 | 68 | 69 | 70 | 71 | 76 | 77 | 85 | 86 | 87 | 88 | 89 | 90 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_tasks.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 16 | 17 | 21 | 22 | 28 | 29 | 30 | 31 | 37 | 38 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_task.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 18 | 19 | 20 | 21 | 27 | 28 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_add_edit_task.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STAR-ZERO/AndroidMVVM/2e9d2b7fe972b292c7859639692b55d7e11f6bf1/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STAR-ZERO/AndroidMVVM/2e9d2b7fe972b292c7859639692b55d7e11f6bf1/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STAR-ZERO/AndroidMVVM/2e9d2b7fe972b292c7859639692b55d7e11f6bf1/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STAR-ZERO/AndroidMVVM/2e9d2b7fe972b292c7859639692b55d7e11f6bf1/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/STAR-ZERO/AndroidMVVM/2e9d2b7fe972b292c7859639692b55d7e11f6bf1/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #000000 8 | #CCCCCC 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidMVVM 3 | 4 | Title 5 | Description 6 | 7 | Title can not be empty 8 | Title is too long 9 | Description is too long 10 | 11 | Task get error 12 | Validation error 13 | Task save error 14 | Delete task error 15 | Change state task error 16 | 17 | Delete 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 |