├── .gitignore
├── .idea
└── runConfigurations.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── bignerdranch
│ │ └── android
│ │ └── testfriendlyarchitecture
│ │ ├── TestReminderApplication.java
│ │ ├── controller
│ │ └── AddReminderTest.java
│ │ ├── fakes
│ │ └── FakeReminderStore.java
│ │ └── inject
│ │ ├── FakeStoreModule.java
│ │ ├── ReminderTestRunner.java
│ │ └── TestComponent.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── bignerdranch
│ │ │ └── android
│ │ │ └── testfriendlyarchitecture
│ │ │ ├── controller
│ │ │ ├── DrawerActivity.java
│ │ │ ├── ReminderApplication.java
│ │ │ ├── create
│ │ │ │ ├── CreateReminderActivity.java
│ │ │ │ └── DateDialogFragment.java
│ │ │ └── list
│ │ │ │ ├── ReminderAdapter.java
│ │ │ │ ├── ReminderListFragment.java
│ │ │ │ └── ReminderViewHolder.java
│ │ │ ├── inject
│ │ │ ├── AdapterModule.java
│ │ │ ├── AppComponent.java
│ │ │ ├── AppModule.java
│ │ │ ├── ModelModule.java
│ │ │ ├── StoreModule.java
│ │ │ └── ViewModelModule.java
│ │ │ ├── model
│ │ │ ├── Reminder.java
│ │ │ ├── date
│ │ │ │ ├── DateFormatter.java
│ │ │ │ ├── DateRange.java
│ │ │ │ ├── DateSelectionListener.java
│ │ │ │ └── DateUtils.java
│ │ │ └── store
│ │ │ │ ├── LiveReminderStore.java
│ │ │ │ ├── ReminderContract.java
│ │ │ │ ├── ReminderCursorWrapper.java
│ │ │ │ ├── ReminderDatabase.java
│ │ │ │ └── ReminderStore.java
│ │ │ └── viewmodel
│ │ │ ├── CreateReminderViewModel.java
│ │ │ └── ReminderItemViewModel.java
│ └── res
│ │ ├── drawable
│ │ ├── ic_add_reminder.xml
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_create_reminder.xml
│ │ ├── activity_drawer.xml
│ │ ├── fragment_date_dialog.xml
│ │ ├── fragment_reminder_list.xml
│ │ ├── item_reminder.xml
│ │ └── navigation_drawer_header.xml
│ │ ├── menu
│ │ └── navigation_drawer_items.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── values-v21
│ │ └── styles.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── bignerdranch
│ └── android
│ └── testfriendlyarchitecture
│ ├── model
│ └── date
│ │ ├── DateFormatterTest.java
│ │ └── DateUtilsTest.java
│ └── viewmodel
│ └── CreateReminderViewModelTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/android,androidstudio
3 |
4 | ### Android ###
5 | # Built application files
6 | *.apk
7 | *.ap_
8 |
9 | # Files for the ART/Dalvik VM
10 | *.dex
11 |
12 | # Java class files
13 | *.class
14 |
15 | # Generated files
16 | bin/
17 | gen/
18 | out/
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 |
24 | # Local configuration file (sdk path, etc)
25 | local.properties
26 |
27 | # Proguard folder generated by Eclipse
28 | proguard/
29 |
30 | # Log Files
31 | *.log
32 |
33 | # Android Studio Navigation editor temp files
34 | .navigation/
35 |
36 | # Android Studio captures folder
37 | captures/
38 |
39 | # Intellij
40 | *.iml
41 | .idea/workspace.xml
42 | .idea/tasks.xml
43 | .idea/gradle.xml
44 | .idea/dictionaries
45 | .idea/libraries
46 |
47 | # Keystore files
48 | *.jks
49 |
50 | # External native build folder generated in Android Studio 2.2 and later
51 | .externalNativeBuild
52 |
53 | # Google Services (e.g. APIs or Firebase)
54 | google-services.json
55 |
56 | # Freeline
57 | freeline.py
58 | freeline/
59 | freeline_project_description.json
60 |
61 | ### Android Patch ###
62 | gen-external-apklibs
63 |
64 | ### AndroidStudio ###
65 | # Covers files to be ignored for android development using Android Studio.
66 |
67 | # Built application files
68 |
69 | # Files for the ART/Dalvik VM
70 |
71 | # Java class files
72 |
73 | # Generated files
74 |
75 | # Gradle files
76 | .gradle
77 |
78 | # Signing files
79 | .signing/
80 |
81 | # Local configuration file (sdk path, etc)
82 |
83 | # Proguard folder generated by Eclipse
84 |
85 | # Log Files
86 |
87 | # Android Studio
88 | /*/build/
89 | /*/local.properties
90 | /*/out
91 | /*/*/build
92 | /*/*/production
93 | *.ipr
94 | *~
95 | *.swp
96 |
97 | # Android Patch
98 |
99 | # External native build folder generated in Android Studio 2.2 and later
100 |
101 | # NDK
102 | obj/
103 |
104 | # IntelliJ IDEA
105 | *.iws
106 | /out/
107 |
108 | # User-specific configurations
109 | .idea/libraries/
110 | .idea/.name
111 | .idea/compiler.xml
112 | .idea/copyright/profiles_settings.xml
113 | .idea/encodings.xml
114 | .idea/misc.xml
115 | .idea/modules.xml
116 | .idea/scopes/scope_settings.xml
117 | .idea/vcs.xml
118 | .idea/jsLibraryMappings.xml
119 | .idea/datasources.xml
120 | .idea/dataSources.ids
121 | .idea/sqlDataSources.xml
122 | .idea/dynamic.xml
123 | .idea/uiDesigner.xml
124 |
125 | # Keystore files
126 |
127 | # OS-specific files
128 | .DS_Store
129 | .DS_Store?
130 | ._*
131 | .Spotlight-V100
132 | .Trashes
133 | ehthumbs.db
134 | Thumbs.db
135 |
136 | # Legacy Eclipse project files
137 | .classpath
138 | .project
139 |
140 | # Mobile Tools for Java (J2ME)
141 | .mtj.tmp/
142 |
143 | # Package Files #
144 | *.jar
145 | *.war
146 | *.ear
147 |
148 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
149 | hs_err_pid*
150 |
151 | ## Plugin-specific files:
152 |
153 | # mpeltonen/sbt-idea plugin
154 | .idea_modules/
155 |
156 | # JIRA plugin
157 | atlassian-ide-plugin.xml
158 |
159 | # Mongo Explorer plugin
160 | .idea/mongoSettings.xml
161 |
162 | # Crashlytics plugin (for Android Studio and IntelliJ)
163 | com_crashlytics_export_strings.xml
164 | crashlytics.properties
165 | crashlytics-build.properties
166 | fabric.properties
167 |
168 | ### AndroidStudio Patch ###
169 | # Google Services plugin
170 |
171 | !/gradle/wrapper/gradle-wrapper.jar
172 |
173 | # End of https://www.gitignore.io/api/android,androidstudio
174 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Test Friendly Architecture
2 |
3 | Sample application I used for the examples in my conference talk at 360AnDev in
4 | Denver. It uses Dagger 2 to inject dependencies in production and uses a Fake
5 | store object in the integration tests.
6 |
7 |
8 | ### Useful Articles and Talks
9 |
10 | [What makes Android apps testable](https://www.philosophicalhacker.com/post/what-makes-android-apps-testable/)
11 |
12 | [Espresso: Beyond the Basics](https://news.realm.io/news/mobilization-inaki-villar-espresso-beyond-the-basics/)
13 |
14 | [Test Driving away Coupling in Activities](https://www.philosophicalhacker.com/post/test-driving-away-coupling-in-activities/)
15 |
16 | [Testing MVP using Espresso and Mockito](https://josiassena.com/testing-mvp-using-espresso-and-mockito/)
17 |
18 | [MVC vs MVP vs MVVM on Android](https://news.realm.io/news/eric-maxwell-mvc-mvp-and-mvvm-on-android)
19 |
20 | [Model-View-Presenter: Android guidelines](https://medium.com/@cervonefrancesco/model-view-presenter-android-guidelines-94970b430ddf)
21 |
22 | [Shades of MVVM](https://www.bignerdranch.com/blog/shades-of-mvvm/)
23 |
24 | [Android Architecture Patterns: Model-View-Presenter](https://upday.github.io/blog/model-view-presenter/)
25 |
26 | [Effective Android Architecture](https://news.realm.io/news/360andev-richa-khandelwal-effective-android-architecture-patterns-java/)
27 |
28 | [Dependency Injection in Android with Dagger 2](https://www.raywenderlich.com/146804/dependency-injection-dagger-2)
29 |
30 | [Dagger 2: Android Modules](https://proandroiddev.com/dagger-2-android-modules-e168821cfc57)
31 |
32 | [Activities Subcomponents Multibinding in Dagger 2](https://medium.com/azimolabs/activities-subcomponents-multibinding-in-dagger-2-85d6053d6a95)
33 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "26.0.0"
6 | defaultConfig {
7 | applicationId "com.bignerdranch.android.testfriendlyarchitecture"
8 | minSdkVersion 19
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "com.bignerdranch.android.testfriendlyarchitecture.inject.ReminderTestRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | dataBinding {
21 | enabled = true
22 | }
23 | compileOptions {
24 | sourceCompatibility 1.8
25 | targetCompatibility 1.8
26 | }
27 | }
28 |
29 | dependencies {
30 | implementation fileTree(dir: 'libs', include: ['*.jar'])
31 | implementation 'com.android.support:appcompat-v7:25.4.0'
32 | implementation 'com.android.support:recyclerview-v7:25.4.0'
33 | implementation 'com.android.support:design:25.4.0'
34 |
35 | // Dagger Dependencies
36 | implementation 'com.google.dagger:dagger:2.11'
37 | annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
38 |
39 | // RxJava
40 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
41 | implementation 'io.reactivex.rxjava2:rxjava:2.1.1'
42 |
43 | // Joda Time
44 | implementation 'joda-time:joda-time:2.9.9'
45 |
46 | testImplementation 'junit:junit:4.12'
47 | testImplementation 'org.mockito:mockito-core:2.8.47'
48 |
49 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
50 | exclude group: 'com.android.support', module: 'support-annotations'
51 | })
52 | androidTestImplementation('com.android.support.test.espresso:espresso-contrib:2.2.2', {
53 | exclude group: 'com.android.support', module: 'appcompat'
54 | exclude module: 'support-annotations'
55 | exclude module: 'support-v4'
56 | exclude module: 'support-v13'
57 | exclude module: 'recyclerview-v7'
58 | exclude module: 'appcompat-v7'
59 | })
60 | androidTestAnnotationProcessor 'com.google.dagger:dagger-compiler:2.11'
61 | }
62 |
--------------------------------------------------------------------------------
/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/bgardner/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 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/bignerdranch/android/testfriendlyarchitecture/TestReminderApplication.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture;
2 |
3 | import android.content.Context;
4 |
5 | import com.bignerdranch.android.testfriendlyarchitecture.controller.ReminderApplication;
6 | import com.bignerdranch.android.testfriendlyarchitecture.inject.AppModule;
7 | import com.bignerdranch.android.testfriendlyarchitecture.inject.DaggerTestComponent;
8 |
9 |
10 | public class TestReminderApplication extends ReminderApplication {
11 |
12 | public static TestReminderApplication get(Context context) {
13 | return (TestReminderApplication) context.getApplicationContext();
14 | }
15 |
16 | @Override
17 | protected void initializeDependencyInjection() {
18 | appComponent = DaggerTestComponent.builder()
19 | .appModule(new AppModule(this))
20 | .build();
21 | }
22 |
23 | @Override
24 | public DaggerTestComponent getAppComponent() {
25 | return (DaggerTestComponent) appComponent;
26 | }
27 |
28 |
29 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/bignerdranch/android/testfriendlyarchitecture/controller/AddReminderTest.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.controller;
2 |
3 | import android.support.test.espresso.ViewInteraction;
4 | import android.support.test.espresso.contrib.PickerActions;
5 | import android.support.test.rule.ActivityTestRule;
6 | import android.support.test.runner.AndroidJUnit4;
7 | import android.test.suitebuilder.annotation.LargeTest;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.view.ViewParent;
11 | import android.widget.DatePicker;
12 |
13 | import com.bignerdranch.android.testfriendlyarchitecture.R;
14 | import com.bignerdranch.android.testfriendlyarchitecture.TestReminderApplication;
15 | import com.bignerdranch.android.testfriendlyarchitecture.model.store.ReminderStore;
16 |
17 | import org.hamcrest.Description;
18 | import org.hamcrest.Matcher;
19 | import org.hamcrest.Matchers;
20 | import org.hamcrest.TypeSafeMatcher;
21 | import org.junit.After;
22 | import org.junit.Before;
23 | import org.junit.Rule;
24 | import org.junit.Test;
25 | import org.junit.runner.RunWith;
26 |
27 | import javax.inject.Inject;
28 |
29 | import static android.support.test.espresso.Espresso.onView;
30 | import static android.support.test.espresso.action.ViewActions.click;
31 | import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
32 | import static android.support.test.espresso.action.ViewActions.replaceText;
33 | import static android.support.test.espresso.assertion.ViewAssertions.matches;
34 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
35 | import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
36 | import static android.support.test.espresso.matcher.ViewMatchers.withId;
37 | import static android.support.test.espresso.matcher.ViewMatchers.withText;
38 | import static org.hamcrest.Matchers.allOf;
39 | import static org.hamcrest.Matchers.is;
40 |
41 | @LargeTest
42 | @RunWith(AndroidJUnit4.class)
43 | public class AddReminderTest {
44 | @Rule
45 | public ActivityTestRule mActivityTestRule =
46 | new ActivityTestRule<>(DrawerActivity.class);
47 | @Inject
48 | ReminderStore reminderStore;
49 |
50 | @Before
51 | public void setup() {
52 | TestReminderApplication.get(mActivityTestRule.getActivity())
53 | .getAppComponent().inject(this);
54 | }
55 |
56 | @After
57 | public void tearDown() {
58 | reminderStore.clearReminders();
59 | }
60 |
61 | @Test
62 | public void addReminderTest() {
63 | // Click on FAB to add new reminder
64 | ViewInteraction floatingActionButton = onView(
65 | allOf(withId(R.id.add_reminder), isDisplayed()));
66 | floatingActionButton.perform(click());
67 |
68 | // Input the new reminder title
69 | ViewInteraction reminderTitleInput = onView(
70 | allOf(childAtPosition(
71 | childAtPosition(
72 | withClassName(is("android.support.design.widget.TextInputLayout")),
73 | 0),
74 | 0),
75 | isDisplayed()));
76 | reminderTitleInput.perform(replaceText("test reminder"), closeSoftKeyboard());
77 |
78 | // Click on date set button to open date picker dialog
79 | ViewInteraction dateSelectionButton = onView(
80 | allOf(withId(R.id.set_date), isDisplayed()));
81 | dateSelectionButton.perform(click());
82 |
83 | // Set the date in date picker using PickerActions from espresso contrib library
84 | onView(withClassName(Matchers.equalTo(DatePicker.class.getName())))
85 | .perform(PickerActions.setDate(2017, 7, 13)); // July 13th 2017
86 |
87 | // click the ok button to dismiss the dialog
88 | ViewInteraction appCompatButton2 = onView(
89 | allOf(withId(R.id.confirm), isDisplayed()));
90 | appCompatButton2.perform(click());
91 |
92 | // click save reminder button to go back to list screen
93 | ViewInteraction appCompatButton3 = onView(
94 | allOf(withId(R.id.save_reminder), withText("Save Reminder"), isDisplayed()));
95 | appCompatButton3.perform(click());
96 |
97 | // verify that the reminder name is correct
98 | ViewInteraction textView = onView(
99 | allOf(withText("test reminder"),
100 | childAtPosition(
101 | childAtPosition(
102 | withId(R.id.reminder_list),
103 | 0),
104 | 0),
105 | isDisplayed()));
106 | textView.check(matches(withText("test reminder")));
107 |
108 | // verify that the reminder date is correct
109 | ViewInteraction textView2 = onView(
110 | allOf(withText("07/13/2017"),
111 | childAtPosition(
112 | childAtPosition(
113 | withId(R.id.reminder_list),
114 | 0),
115 | 1),
116 | isDisplayed()));
117 | textView2.check(matches(withText("07/13/2017")));
118 | }
119 |
120 | private static Matcher childAtPosition(
121 | final Matcher parentMatcher, final int position) {
122 |
123 | return new TypeSafeMatcher() {
124 | @Override
125 | public void describeTo(Description description) {
126 | description.appendText("Child at position " + position + " in parent ");
127 | parentMatcher.describeTo(description);
128 | }
129 |
130 | @Override
131 | public boolean matchesSafely(View view) {
132 | ViewParent parent = view.getParent();
133 | return parent instanceof ViewGroup && parentMatcher.matches(parent)
134 | && view.equals(((ViewGroup) parent).getChildAt(position));
135 | }
136 | };
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/bignerdranch/android/testfriendlyarchitecture/fakes/FakeReminderStore.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.fakes;
2 |
3 | import com.bignerdranch.android.testfriendlyarchitecture.model.Reminder;
4 | import com.bignerdranch.android.testfriendlyarchitecture.model.store.ReminderStore;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | public class FakeReminderStore implements ReminderStore {
10 |
11 | private List reminders;
12 |
13 | public FakeReminderStore() {
14 | reminders = new ArrayList<>();
15 | }
16 |
17 | @Override
18 | public List getReminders() {
19 | return reminders;
20 | }
21 |
22 | @Override
23 | public void addReminder(Reminder reminder) {
24 | reminders.add(reminder);
25 | }
26 |
27 | @Override
28 | public void clearReminders() {
29 | reminders = new ArrayList<>();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/bignerdranch/android/testfriendlyarchitecture/inject/FakeStoreModule.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.inject;
2 |
3 | import com.bignerdranch.android.testfriendlyarchitecture.fakes.FakeReminderStore;
4 | import com.bignerdranch.android.testfriendlyarchitecture.model.store.ReminderStore;
5 |
6 | import javax.inject.Singleton;
7 |
8 | import dagger.Module;
9 | import dagger.Provides;
10 |
11 | @Module
12 | @Singleton
13 | public class FakeStoreModule {
14 |
15 | @Provides
16 | @Singleton
17 | ReminderStore providesReminderStore() {
18 | return new FakeReminderStore();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/bignerdranch/android/testfriendlyarchitecture/inject/ReminderTestRunner.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.inject;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.support.test.runner.AndroidJUnitRunner;
6 |
7 | import com.bignerdranch.android.testfriendlyarchitecture.TestReminderApplication;
8 |
9 | public class ReminderTestRunner extends AndroidJUnitRunner {
10 | @Override
11 | public Application newApplication(ClassLoader cl,
12 | String className,
13 | Context context)
14 | throws InstantiationException, IllegalAccessException, ClassNotFoundException {
15 | return super.newApplication(cl, TestReminderApplication.class.getName(), context);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/bignerdranch/android/testfriendlyarchitecture/inject/TestComponent.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.inject;
2 |
3 | import com.bignerdranch.android.testfriendlyarchitecture.controller.AddReminderTest;
4 |
5 | import javax.inject.Singleton;
6 |
7 | import dagger.Component;
8 |
9 | @Component(modules = {AppModule.class, ViewModelModule.class,
10 | FakeStoreModule.class, AdapterModule.class, ModelModule.class})
11 | @Singleton
12 | interface TestComponent extends AppComponent {
13 | void inject(AddReminderTest test);
14 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/controller/DrawerActivity.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.controller;
2 |
3 | import android.databinding.DataBindingUtil;
4 | import android.os.Bundle;
5 | import android.support.design.widget.NavigationView;
6 | import android.support.v4.app.Fragment;
7 | import android.support.v7.app.ActionBarDrawerToggle;
8 | import android.support.v7.app.AppCompatActivity;
9 | import android.view.View;
10 |
11 | import com.bignerdranch.android.testfriendlyarchitecture.R;
12 | import com.bignerdranch.android.testfriendlyarchitecture.controller.create.CreateReminderActivity;
13 | import com.bignerdranch.android.testfriendlyarchitecture.controller.list.ReminderListFragment;
14 | import com.bignerdranch.android.testfriendlyarchitecture.databinding.ActivityDrawerBinding;
15 | import com.bignerdranch.android.testfriendlyarchitecture.model.date.DateRange;
16 |
17 | public class DrawerActivity extends AppCompatActivity {
18 | private ActivityDrawerBinding binding;
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | binding = DataBindingUtil.setContentView(this, R.layout.activity_drawer);
24 | // setup toolbar from layout
25 | setSupportActionBar(binding.toolbar);
26 | // setup hamburger menu
27 | ActionBarDrawerToggle toggle = createDrawerToggle();
28 | binding.drawerLayout.addDrawerListener(toggle);
29 | toggle.syncState();
30 | // setup navigation item selection listener
31 | binding.navigation.setNavigationItemSelectedListener(listener);
32 | // set click listener on FAB
33 | binding.addReminder.setOnClickListener(addReminderListener);
34 | // setup initial screen on first launch
35 | if (savedInstanceState == null) {
36 | updateFragment(ReminderListFragment.newInstance(DateRange.ALL));
37 | }
38 | }
39 |
40 | private ActionBarDrawerToggle createDrawerToggle() {
41 | return new ActionBarDrawerToggle(this, binding.drawerLayout, binding.toolbar,
42 | R.string.content_description_open_drawer,
43 | R.string.content_description_close_drawer);
44 | }
45 |
46 | private NavigationView.OnNavigationItemSelectedListener listener = menuItem -> {
47 | binding.drawerLayout.closeDrawers();
48 | ReminderListFragment fragment;
49 | switch (menuItem.getItemId()) {
50 | case R.id.reminders_all:
51 | fragment = ReminderListFragment.newInstance(DateRange.ALL);
52 | break;
53 | case R.id.reminders_today:
54 | fragment = ReminderListFragment.newInstance(DateRange.TODAY);
55 | break;
56 | case R.id.reminders_week:
57 | fragment = ReminderListFragment.newInstance(DateRange.WEEK);
58 | break;
59 | default:
60 | throw new IllegalArgumentException("Unknown navigation item selected!");
61 | }
62 | getSupportFragmentManager().beginTransaction()
63 | .replace(binding.fragmentContainer.getId(), fragment)
64 | .commit();
65 | return true;
66 | };
67 |
68 | private View.OnClickListener addReminderListener = view -> {
69 | // start the create activity
70 | startActivity(CreateReminderActivity.newIntent(this));
71 | };
72 |
73 | private void updateFragment(Fragment fragment) {
74 | getSupportFragmentManager().beginTransaction()
75 | .replace(binding.fragmentContainer.getId(), fragment)
76 | .commit();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/controller/ReminderApplication.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.controller;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 |
6 | import com.bignerdranch.android.testfriendlyarchitecture.inject.AdapterModule;
7 | import com.bignerdranch.android.testfriendlyarchitecture.inject.AppComponent;
8 | import com.bignerdranch.android.testfriendlyarchitecture.inject.AppModule;
9 | import com.bignerdranch.android.testfriendlyarchitecture.inject.DaggerAppComponent;
10 | import com.bignerdranch.android.testfriendlyarchitecture.inject.StoreModule;
11 | import com.bignerdranch.android.testfriendlyarchitecture.inject.ViewModelModule;
12 |
13 | public class ReminderApplication extends Application {
14 |
15 | protected AppComponent appComponent;
16 |
17 | public static ReminderApplication get(Context context) {
18 | return (ReminderApplication) context.getApplicationContext();
19 | }
20 |
21 | @Override
22 | public void onCreate() {
23 | super.onCreate();
24 | initializeDependencyInjection();
25 | }
26 |
27 | public AppComponent getAppComponent() {
28 | return appComponent;
29 | }
30 |
31 | protected void initializeDependencyInjection() {
32 | appComponent = DaggerAppComponent.builder()
33 | .appModule(new AppModule(this))
34 | .viewModelModule(new ViewModelModule())
35 | .storeModule(new StoreModule())
36 | .adapterModule(new AdapterModule())
37 | .build();
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/controller/create/CreateReminderActivity.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.controller.create;
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.v7.app.AppCompatActivity;
8 | import android.view.View;
9 |
10 | import com.bignerdranch.android.testfriendlyarchitecture.R;
11 | import com.bignerdranch.android.testfriendlyarchitecture.controller.ReminderApplication;
12 | import com.bignerdranch.android.testfriendlyarchitecture.databinding.ActivityCreateReminderBinding;
13 | import com.bignerdranch.android.testfriendlyarchitecture.model.date.DateSelectionListener;
14 | import com.bignerdranch.android.testfriendlyarchitecture.model.store.ReminderStore;
15 | import com.bignerdranch.android.testfriendlyarchitecture.viewmodel.CreateReminderViewModel;
16 |
17 | import org.joda.time.LocalDate;
18 |
19 | import javax.inject.Inject;
20 |
21 | public class CreateReminderActivity extends AppCompatActivity
22 | implements DateSelectionListener {
23 | private static final String TAG_DATE_FRAGMENT = "tag_date_fragment";
24 |
25 | private ActivityCreateReminderBinding binding;
26 | @Inject
27 | protected CreateReminderViewModel viewModel;
28 | @Inject
29 | protected ReminderStore reminderStore;
30 |
31 | public static Intent newIntent(Context context) {
32 | return new Intent(context, CreateReminderActivity.class);
33 | }
34 |
35 | @Override
36 | protected void onCreate(Bundle savedInstanceState) {
37 | super.onCreate(savedInstanceState);
38 | // Inject dependencies
39 | ReminderApplication.get(this).getAppComponent().inject(this);
40 | // Setup view
41 | binding = DataBindingUtil.setContentView(this, R.layout.activity_create_reminder);
42 | binding.setReminder(viewModel);
43 | binding.setDate.setOnClickListener(setDateListener);
44 | binding.saveReminder.setOnClickListener(saveReminderListener);
45 | }
46 |
47 | private View.OnClickListener setDateListener = view -> {
48 | DateDialogFragment fragment = DateDialogFragment.newInstance(viewModel.getReminder().getDate());
49 | getSupportFragmentManager().beginTransaction()
50 | .add(fragment, TAG_DATE_FRAGMENT)
51 | .commit();
52 | };
53 |
54 | private View.OnClickListener saveReminderListener = view -> {
55 | reminderStore.addReminder(viewModel.getReminder());
56 | finish();
57 | };
58 |
59 | @Override
60 | public void onDateSelected(LocalDate date) {
61 | viewModel.setDate(date);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/controller/create/DateDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.controller.create;
2 |
3 | import android.content.Context;
4 | import android.databinding.DataBindingUtil;
5 | import android.os.Bundle;
6 | import android.support.annotation.Nullable;
7 | import android.support.v4.app.DialogFragment;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.DatePicker;
12 |
13 | import com.bignerdranch.android.testfriendlyarchitecture.R;
14 | import com.bignerdranch.android.testfriendlyarchitecture.databinding.FragmentDateDialogBinding;
15 | import com.bignerdranch.android.testfriendlyarchitecture.model.date.DateSelectionListener;
16 |
17 | import org.joda.time.LocalDate;
18 |
19 | public class DateDialogFragment extends DialogFragment {
20 | private static final String ARG_DATE = "arg_date";
21 |
22 | private FragmentDateDialogBinding binding;
23 | private DateSelectionListener listener;
24 | private LocalDate currentDate;
25 |
26 |
27 | public static DateDialogFragment newInstance(LocalDate date) {
28 | Bundle args = new Bundle();
29 | args.putSerializable(ARG_DATE, date);
30 | DateDialogFragment fragment = new DateDialogFragment();
31 | fragment.setArguments(args);
32 | return fragment;
33 | }
34 |
35 | @Override
36 | public void onCreate(@Nullable Bundle savedInstanceState) {
37 | super.onCreate(savedInstanceState);
38 | currentDate = (LocalDate) getArguments().getSerializable(ARG_DATE);
39 | if (currentDate == null) {
40 | currentDate = new LocalDate();
41 | }
42 | }
43 |
44 | @Nullable
45 | @Override
46 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
47 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_date_dialog, container, false);
48 | binding.confirm.setOnClickListener(confirmListener);
49 | binding.datePicker.init(
50 | currentDate.getYear(),
51 | currentDate.getMonthOfYear() - 1, // date picker expects 0-11 month, jodatime supplies 1-12
52 | currentDate.getDayOfMonth(),
53 | dateChangedListener);
54 |
55 | return binding.getRoot();
56 | }
57 |
58 | @Override
59 | public void onAttach(Context context) {
60 | super.onAttach(context);
61 | listener = (DateSelectionListener) context;
62 | }
63 |
64 | @Override
65 | public void onDetach() {
66 | super.onDetach();
67 | listener = null;
68 | }
69 |
70 | private View.OnClickListener confirmListener = view -> {
71 | listener.onDateSelected(currentDate);
72 | dismiss();
73 | };
74 |
75 | private DatePicker.OnDateChangedListener dateChangedListener = new DatePicker.OnDateChangedListener() {
76 | @Override
77 | public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
78 | int jodaMonth = monthOfYear + 1; // date picker expects 0-11 month, jodatime supplies 1-12
79 | currentDate = new LocalDate(year, jodaMonth, dayOfMonth);
80 | }
81 | };
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/controller/list/ReminderAdapter.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.controller.list;
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.ViewGroup;
8 |
9 | import com.bignerdranch.android.testfriendlyarchitecture.R;
10 | import com.bignerdranch.android.testfriendlyarchitecture.databinding.ItemReminderBinding;
11 | import com.bignerdranch.android.testfriendlyarchitecture.model.Reminder;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | public class ReminderAdapter extends RecyclerView.Adapter {
17 |
18 | private Context context;
19 | private List reminders;
20 |
21 | public ReminderAdapter(Context context) {
22 | this.context = context;
23 | this.reminders = new ArrayList<>();
24 | }
25 |
26 | @Override
27 | public ReminderViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
28 | LayoutInflater inflater = LayoutInflater.from(context);
29 | ItemReminderBinding binding = DataBindingUtil.inflate(inflater, R.layout.item_reminder, parent, false);
30 | return new ReminderViewHolder(binding, context);
31 | }
32 |
33 | @Override
34 | public void onBindViewHolder(ReminderViewHolder holder, int position) {
35 | holder.bindReminder(reminders.get(position));
36 | }
37 |
38 | @Override
39 | public int getItemCount() {
40 | return reminders.size();
41 | }
42 |
43 | public void setReminders(List reminders) {
44 | this.reminders = reminders;
45 | notifyDataSetChanged();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/controller/list/ReminderListFragment.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.controller.list;
2 |
3 | import android.databinding.DataBindingUtil;
4 | import android.os.Bundle;
5 | import android.support.annotation.Nullable;
6 | import android.support.v4.app.Fragment;
7 | import android.support.v7.widget.LinearLayoutManager;
8 | import android.util.Log;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 |
13 | import com.bignerdranch.android.testfriendlyarchitecture.R;
14 | import com.bignerdranch.android.testfriendlyarchitecture.controller.ReminderApplication;
15 | import com.bignerdranch.android.testfriendlyarchitecture.databinding.FragmentReminderListBinding;
16 | import com.bignerdranch.android.testfriendlyarchitecture.model.Reminder;
17 | import com.bignerdranch.android.testfriendlyarchitecture.model.date.DateRange;
18 | import com.bignerdranch.android.testfriendlyarchitecture.model.date.DateUtils;
19 | import com.bignerdranch.android.testfriendlyarchitecture.model.store.ReminderStore;
20 |
21 | import java.util.List;
22 |
23 | import javax.inject.Inject;
24 |
25 | import io.reactivex.Observable;
26 | import io.reactivex.SingleObserver;
27 | import io.reactivex.disposables.Disposable;
28 |
29 | public class ReminderListFragment extends Fragment {
30 | private static final String TAG = "ReminderListFragment";
31 | private static final String ARG_DATE_RANGE = "arg_date_range";
32 |
33 | private FragmentReminderListBinding binding;
34 | private DateRange range;
35 | @Inject
36 | ReminderAdapter adapter;
37 | @Inject
38 | ReminderStore reminderStore;
39 | @Inject
40 | DateUtils dateUtils;
41 |
42 | public static ReminderListFragment newInstance(DateRange range) {
43 | Bundle args = new Bundle();
44 | args.putSerializable(ARG_DATE_RANGE, range);
45 | ReminderListFragment fragment = new ReminderListFragment();
46 | fragment.setArguments(args);
47 | return fragment;
48 | }
49 |
50 | @Override
51 | public void onCreate(@Nullable Bundle savedInstanceState) {
52 | super.onCreate(savedInstanceState);
53 | ReminderApplication.get(getContext()).getAppComponent().inject(this);
54 | range = (DateRange) getArguments().getSerializable(ARG_DATE_RANGE);
55 | }
56 |
57 | @Nullable
58 | @Override
59 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
60 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_reminder_list, container, false);
61 | binding.reminderList.setLayoutManager(new LinearLayoutManager(getContext()));
62 | binding.reminderList.setAdapter(adapter);
63 | return binding.getRoot();
64 | }
65 |
66 | @Override
67 | public void onResume() {
68 | super.onResume();
69 | setupAdapter();
70 | }
71 |
72 | private void setupAdapter() {
73 | Observable.fromIterable(reminderStore.getReminders())
74 | .filter(this::isReminderInRange)
75 | .toList()
76 | .subscribe(new SingleObserver>() {
77 | @Override
78 | public void onSubscribe(Disposable d) {
79 | }
80 |
81 | @Override
82 | public void onSuccess(List reminders) {
83 | adapter.setReminders(reminders);
84 | }
85 |
86 | @Override
87 | public void onError(Throwable e) {
88 | Log.e(TAG, "onError: could not setup adapter", e);
89 | }
90 | });
91 | }
92 |
93 | private boolean isReminderInRange(Reminder reminder) {
94 | switch (range) {
95 | case ALL:
96 | return true;
97 | case TODAY:
98 | return dateUtils.isToday(reminder.getDate());
99 | case WEEK:
100 | return dateUtils.isThisWeek(reminder.getDate());
101 | default:
102 | throw new IllegalArgumentException("Unknown date range specified");
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/controller/list/ReminderViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.controller.list;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 |
6 | import com.bignerdranch.android.testfriendlyarchitecture.controller.ReminderApplication;
7 | import com.bignerdranch.android.testfriendlyarchitecture.databinding.ItemReminderBinding;
8 | import com.bignerdranch.android.testfriendlyarchitecture.model.Reminder;
9 | import com.bignerdranch.android.testfriendlyarchitecture.viewmodel.ReminderItemViewModel;
10 |
11 | import javax.inject.Inject;
12 |
13 | public class ReminderViewHolder extends RecyclerView.ViewHolder {
14 |
15 | private ItemReminderBinding binding;
16 | @Inject
17 | ReminderItemViewModel viewModel;
18 |
19 | public ReminderViewHolder(ItemReminderBinding binding, Context context) {
20 | super(binding.getRoot());
21 | ReminderApplication.get(context).getAppComponent().inject(this);
22 | this.binding = binding;
23 | binding.setReminder(viewModel);
24 | }
25 |
26 | public void bindReminder(Reminder reminder) {
27 | viewModel.setReminder(reminder);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/inject/AdapterModule.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.inject;
2 |
3 | import android.content.Context;
4 |
5 | import com.bignerdranch.android.testfriendlyarchitecture.controller.list.ReminderAdapter;
6 |
7 | import javax.inject.Singleton;
8 |
9 | import dagger.Module;
10 | import dagger.Provides;
11 |
12 | @Module
13 | @Singleton
14 | public class AdapterModule {
15 |
16 | @Provides
17 | ReminderAdapter providesReminderAdapter(Context context) {
18 | return new ReminderAdapter(context);
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/inject/AppComponent.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.inject;
2 |
3 | import com.bignerdranch.android.testfriendlyarchitecture.controller.create.CreateReminderActivity;
4 | import com.bignerdranch.android.testfriendlyarchitecture.controller.list.ReminderListFragment;
5 | import com.bignerdranch.android.testfriendlyarchitecture.controller.list.ReminderViewHolder;
6 |
7 | import javax.inject.Singleton;
8 |
9 | import dagger.Component;
10 |
11 | @Component(modules = {AppModule.class, ViewModelModule.class,
12 | StoreModule.class, AdapterModule.class, ModelModule.class})
13 | @Singleton
14 | public interface AppComponent {
15 | // Activity injection
16 | void inject(CreateReminderActivity activity);
17 |
18 | // Fragment injection
19 | void inject(ReminderListFragment fragment);
20 |
21 | // View holder injection
22 | void inject(ReminderViewHolder viewHolder);
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/inject/AppModule.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.inject;
2 |
3 | import android.content.Context;
4 |
5 | import com.bignerdranch.android.testfriendlyarchitecture.model.date.DateFormatter;
6 | import com.bignerdranch.android.testfriendlyarchitecture.model.date.DateUtils;
7 |
8 | import javax.inject.Singleton;
9 |
10 | import dagger.Module;
11 | import dagger.Provides;
12 |
13 | @Module
14 | @Singleton
15 | public class AppModule {
16 |
17 | private Context appContext;
18 |
19 | public AppModule(Context context) {
20 | appContext = context.getApplicationContext();
21 | }
22 |
23 | @Provides
24 | Context providesAppContext() {
25 | return appContext;
26 | }
27 |
28 | @Provides
29 | DateFormatter providesDateFormatter() {
30 | return new DateFormatter();
31 | }
32 |
33 | @Provides
34 | DateUtils providesDateUtils() {
35 | return new DateUtils();
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/inject/ModelModule.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.inject;
2 |
3 | import com.bignerdranch.android.testfriendlyarchitecture.model.Reminder;
4 |
5 | import javax.inject.Singleton;
6 |
7 | import dagger.Module;
8 | import dagger.Provides;
9 |
10 | @Module
11 | @Singleton
12 | public class ModelModule {
13 |
14 | @Provides
15 | Reminder providesReminder() {
16 | return new Reminder();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/inject/StoreModule.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.inject;
2 |
3 | import android.content.Context;
4 |
5 | import com.bignerdranch.android.testfriendlyarchitecture.model.store.LiveReminderStore;
6 | import com.bignerdranch.android.testfriendlyarchitecture.model.store.ReminderDatabase;
7 | import com.bignerdranch.android.testfriendlyarchitecture.model.store.ReminderStore;
8 |
9 | import javax.inject.Named;
10 | import javax.inject.Singleton;
11 |
12 | import dagger.Module;
13 | import dagger.Provides;
14 |
15 | @Module
16 | @Singleton
17 | public class StoreModule {
18 | private static final String DATABASE_FILE_NAME = "reminders.db";
19 | private static final int DATABASE_VERSION_NUMBER = 1;
20 |
21 | @Provides
22 | @Singleton
23 | ReminderStore providesReminderStore(ReminderDatabase database) {
24 | return new LiveReminderStore(database);
25 | }
26 |
27 | /**
28 | * The provides methods below are internal to this module.
29 | * They are only used to supply the ReminderDatabase parameter to the ReminderStore
30 | */
31 | @Provides
32 | ReminderDatabase providesReminderDatabase(Context context,
33 | @Named("database_file") String filename,
34 | @Named("database_version") int version) {
35 | return new ReminderDatabase(context, filename, null, version);
36 | }
37 |
38 | @Provides
39 | @Named("database_file")
40 | String providesDatabaseFileName() {
41 | return DATABASE_FILE_NAME;
42 | }
43 |
44 | @Provides
45 | @Named("database_version")
46 | int providesDatabaseVersion() {
47 | return DATABASE_VERSION_NUMBER;
48 | }
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/inject/ViewModelModule.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.inject;
2 |
3 | import android.content.Context;
4 |
5 | import com.bignerdranch.android.testfriendlyarchitecture.model.Reminder;
6 | import com.bignerdranch.android.testfriendlyarchitecture.model.date.DateFormatter;
7 | import com.bignerdranch.android.testfriendlyarchitecture.viewmodel.CreateReminderViewModel;
8 | import com.bignerdranch.android.testfriendlyarchitecture.viewmodel.ReminderItemViewModel;
9 |
10 | import javax.inject.Singleton;
11 |
12 | import dagger.Module;
13 | import dagger.Provides;
14 |
15 | @Module
16 | @Singleton
17 | public class ViewModelModule {
18 | @Provides
19 | CreateReminderViewModel providesCreateReminderViewModel(Context context,
20 | DateFormatter dateFormatter,
21 | Reminder reminder) {
22 | return new CreateReminderViewModel(context, dateFormatter, reminder);
23 | }
24 |
25 | @Provides
26 | ReminderItemViewModel providesReminderItemViewModel(DateFormatter dateFormatter) {
27 | return new ReminderItemViewModel(dateFormatter);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/model/Reminder.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.model;
2 |
3 | import org.joda.time.LocalDate;
4 |
5 | public class Reminder {
6 |
7 | private int id;
8 | private String title;
9 | private LocalDate date;
10 |
11 | public Reminder() {
12 | this(null, new LocalDate());
13 | }
14 |
15 | public Reminder(String title, LocalDate date) {
16 | this(0, title, date);
17 | }
18 |
19 | public Reminder(int id, String title, LocalDate date) {
20 | this.id = id;
21 | this.title = title;
22 | this.date = date;
23 | }
24 |
25 | public int getId() {
26 | return id;
27 | }
28 |
29 | public String getTitle() {
30 | return title;
31 | }
32 |
33 | public void setTitle(String title) {
34 | this.title = title;
35 | }
36 |
37 | public LocalDate getDate() {
38 | return date;
39 | }
40 |
41 | public void setDate(LocalDate date) {
42 | this.date = date;
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/model/date/DateFormatter.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.model.date;
2 |
3 | import org.joda.time.LocalDate;
4 | import org.joda.time.format.DateTimeFormat;
5 | import org.joda.time.format.DateTimeFormatter;
6 |
7 | public class DateFormatter {
8 | private static final String FORMAT_STRING = "MM/dd/yyyy";
9 | private static final String NULL_RETURN = "";
10 |
11 | public String format(LocalDate date) {
12 | if (date == null) {
13 | return NULL_RETURN;
14 | }
15 | DateTimeFormatter formatter = DateTimeFormat.forPattern(FORMAT_STRING);
16 | return formatter.print(date);
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/model/date/DateRange.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.model.date;
2 |
3 | import java.io.Serializable;
4 |
5 | public enum DateRange implements Serializable {
6 | ALL,
7 | TODAY,
8 | WEEK
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/model/date/DateSelectionListener.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.model.date;
2 |
3 | import org.joda.time.LocalDate;
4 |
5 | public interface DateSelectionListener {
6 | void onDateSelected(LocalDate date);
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/model/date/DateUtils.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.model.date;
2 |
3 | import org.joda.time.LocalDate;
4 |
5 | public class DateUtils {
6 |
7 | public boolean isToday(LocalDate date) {
8 | LocalDate today = new LocalDate();
9 | return date.equals(today);
10 | }
11 |
12 | public boolean isThisWeek(LocalDate date) {
13 | LocalDate today = new LocalDate();
14 | LocalDate nextWeek = today.plusDays(7);
15 | return !date.isBefore(today) && !date.isAfter(nextWeek);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/model/store/LiveReminderStore.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.model.store;
2 |
3 | import android.content.ContentValues;
4 | import android.database.Cursor;
5 |
6 | import com.bignerdranch.android.testfriendlyarchitecture.model.Reminder;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | public class LiveReminderStore implements ReminderStore {
12 |
13 | private ReminderDatabase database;
14 | private List reminders;
15 |
16 | public LiveReminderStore(ReminderDatabase database) {
17 | this.database = database;
18 | }
19 |
20 | @Override
21 | public List getReminders() {
22 | if (reminders == null) {
23 | reminders = loadReminders();
24 | }
25 | return reminders;
26 | }
27 |
28 | @Override
29 | public void addReminder(Reminder reminder) {
30 | // add to in memory cache
31 | reminders.add(reminder);
32 | // write to database
33 | ContentValues values = getContentValues(reminder);
34 | database.getWritableDatabase()
35 | .insert(ReminderContract.TABLE_NAME, null, values);
36 | }
37 |
38 | @Override
39 | public void clearReminders() {
40 | reminders = new ArrayList<>();
41 | database.getWritableDatabase().execSQL("DELETE FROM " + ReminderContract.TABLE_NAME);
42 | }
43 |
44 | private List loadReminders() {
45 | Cursor cursor = database.getReadableDatabase()
46 | .query(ReminderContract.TABLE_NAME, null, null, null, null, null, null);
47 | ReminderCursorWrapper wrapper = new ReminderCursorWrapper(cursor);
48 | List reminders = new ArrayList<>();
49 | try {
50 | wrapper.moveToFirst();
51 | while (!wrapper.isAfterLast()) {
52 | reminders.add(wrapper.getReminder());
53 | wrapper.moveToNext();
54 | }
55 | } finally {
56 | wrapper.close();
57 | cursor.close();
58 | }
59 | return reminders;
60 | }
61 |
62 | private ContentValues getContentValues(Reminder reminder) {
63 | ContentValues values = new ContentValues();
64 | values.put(ReminderContract.Cols.TITLE, reminder.getTitle());
65 | Long date;
66 | if (reminder.getDate() != null) {
67 | date = reminder.getDate().toDate().getTime();
68 | } else {
69 | date = null;
70 | }
71 | values.put(ReminderContract.Cols.DATE, date);
72 | return values;
73 | }
74 | }
75 |
76 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/model/store/ReminderContract.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.model.store;
2 |
3 | public class ReminderContract {
4 |
5 | public static final String TABLE_NAME = "reminders";
6 |
7 | public static class Cols {
8 | public static final String ID = "_id";
9 | public static final String TITLE = "title";
10 | public static final String DATE = "date";
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/model/store/ReminderCursorWrapper.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.model.store;
2 |
3 | import android.database.Cursor;
4 | import android.database.CursorWrapper;
5 |
6 | import com.bignerdranch.android.testfriendlyarchitecture.model.Reminder;
7 |
8 | import org.joda.time.LocalDate;
9 |
10 | public class ReminderCursorWrapper extends CursorWrapper {
11 | public ReminderCursorWrapper(Cursor cursor) {
12 | super(cursor);
13 | }
14 |
15 | public Reminder getReminder() {
16 | int id = getInt(getColumnIndex(ReminderContract.Cols.ID));
17 | String title = getString(getColumnIndex(ReminderContract.Cols.TITLE));
18 | long date = getLong(getColumnIndex(ReminderContract.Cols.DATE));
19 |
20 | return new Reminder(id, title, new LocalDate(date));
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/model/store/ReminderDatabase.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.model.store;
2 |
3 | import android.content.Context;
4 | import android.database.sqlite.SQLiteDatabase;
5 | import android.database.sqlite.SQLiteOpenHelper;
6 |
7 | public class ReminderDatabase extends SQLiteOpenHelper {
8 |
9 | public ReminderDatabase(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
10 | super(context, name, factory, version);
11 | }
12 |
13 | @Override
14 | public void onCreate(SQLiteDatabase db) {
15 | db.execSQL("CREATE TABLE " + ReminderContract.TABLE_NAME + "(" +
16 | ReminderContract.Cols.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
17 | ReminderContract.Cols.TITLE + " TEXT," +
18 | ReminderContract.Cols.DATE + " INT)");
19 | }
20 |
21 | @Override
22 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/model/store/ReminderStore.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.model.store;
2 |
3 | import com.bignerdranch.android.testfriendlyarchitecture.model.Reminder;
4 |
5 | import java.util.List;
6 |
7 | public interface ReminderStore {
8 | List getReminders();
9 | void addReminder(Reminder reminder);
10 | void clearReminders();
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/viewmodel/CreateReminderViewModel.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.viewmodel;
2 |
3 | import android.content.Context;
4 | import android.databinding.BaseObservable;
5 | import android.databinding.Bindable;
6 |
7 | import com.bignerdranch.android.testfriendlyarchitecture.BR;
8 | import com.bignerdranch.android.testfriendlyarchitecture.R;
9 | import com.bignerdranch.android.testfriendlyarchitecture.model.Reminder;
10 | import com.bignerdranch.android.testfriendlyarchitecture.model.date.DateFormatter;
11 |
12 | import org.joda.time.LocalDate;
13 |
14 | public class CreateReminderViewModel extends BaseObservable {
15 | private Context context;
16 | private DateFormatter dateFormatter;
17 | private Reminder reminder;
18 |
19 | public CreateReminderViewModel(Context context,
20 | DateFormatter dateFormatter,
21 | Reminder reminder) {
22 | this.context = context;
23 | this.dateFormatter = dateFormatter;
24 | this.reminder = reminder;
25 | }
26 |
27 | public Reminder getReminder() {
28 | return reminder;
29 | }
30 |
31 | public String getTitle() {
32 | return reminder.getTitle();
33 | }
34 |
35 | public void setTitle(String title) {
36 | reminder.setTitle(title);
37 | }
38 |
39 | @Bindable
40 | public String getDate() {
41 | LocalDate date = reminder.getDate();
42 | if (date != null) {
43 | return dateFormatter.format(date);
44 | }
45 | return context.getString(R.string.reminder_set_date);
46 | }
47 |
48 | public void setDate(LocalDate date) {
49 | reminder.setDate(date);
50 | notifyPropertyChanged(BR.date);
51 | }
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bignerdranch/android/testfriendlyarchitecture/viewmodel/ReminderItemViewModel.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.viewmodel;
2 |
3 | import android.databinding.BaseObservable;
4 | import android.databinding.Bindable;
5 |
6 | import com.bignerdranch.android.testfriendlyarchitecture.model.Reminder;
7 | import com.bignerdranch.android.testfriendlyarchitecture.model.date.DateFormatter;
8 |
9 | public class ReminderItemViewModel extends BaseObservable {
10 |
11 | private DateFormatter dateFormatter;
12 | private Reminder reminder;
13 |
14 | public ReminderItemViewModel(DateFormatter dateFormatter) {
15 | this.dateFormatter = dateFormatter;
16 | }
17 |
18 | public void setReminder(Reminder reminder) {
19 | this.reminder = reminder;
20 | notifyChange();
21 | }
22 |
23 | @Bindable
24 | public String getTitle() {
25 | return reminder.getTitle();
26 | }
27 |
28 | @Bindable
29 | public String getDate() {
30 | return dateFormatter.format(reminder.getDate());
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_reminder.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
17 |
22 |
27 |
32 |
37 |
42 |
47 |
52 |
57 |
62 |
67 |
72 |
77 |
82 |
87 |
92 |
97 |
102 |
107 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_create_reminder.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
15 |
16 |
19 |
20 |
25 |
26 |
27 |
32 |
33 |
37 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
10 |
11 |
15 |
16 |
23 |
24 |
28 |
29 |
37 |
38 |
39 |
40 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_date_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
12 |
13 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_reminder_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_reminder.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
13 |
14 |
19 |
20 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/navigation_drawer_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/navigation_drawer_items.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 56dp
4 | 16dp
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Reminders
3 |
4 |
5 | Open navigation drawer
6 | Close navigation drawer
7 | Lists
8 | All
9 | Today
10 | Week
11 |
12 |
13 | All Reminders
14 | Today\'s Reminders
15 | Week\'s Reminders
16 | New Reminder
17 |
18 |
19 | Reminder text
20 | Set Date
21 | Save Reminder
22 |
23 |
24 | Select Reminder Date
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
16 |
17 |
22 |
23 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/test/java/com/bignerdranch/android/testfriendlyarchitecture/model/date/DateFormatterTest.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.model.date;
2 |
3 | import org.joda.time.LocalDate;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.junit.runners.JUnit4;
8 |
9 | import static org.hamcrest.CoreMatchers.equalTo;
10 | import static org.hamcrest.CoreMatchers.is;
11 | import static org.hamcrest.MatcherAssert.assertThat;
12 |
13 | @RunWith(JUnit4.class)
14 | public class DateFormatterTest {
15 |
16 | private DateFormatter dateFormatter;
17 |
18 | @Before
19 | public void setup() {
20 | dateFormatter = new DateFormatter();
21 | }
22 |
23 | @Test
24 | public void itCorrectlyFormatsADate() {
25 | LocalDate date = new LocalDate(2017, 7, 13);
26 | String expectedDateFormat = "07/13/2017";
27 | assertThat(dateFormatter.format(date), is(equalTo(expectedDateFormat)));
28 | }
29 |
30 | @Test
31 | public void itReturnsAnEmptyStringOnNullParameter() {
32 | String expectedDateFormat = "";
33 | assertThat(dateFormatter.format(null), is(equalTo(expectedDateFormat)));
34 | }
35 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/bignerdranch/android/testfriendlyarchitecture/model/date/DateUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.model.date;
2 |
3 | import org.joda.time.LocalDate;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.junit.runners.JUnit4;
8 |
9 | import static org.hamcrest.CoreMatchers.equalTo;
10 | import static org.hamcrest.CoreMatchers.is;
11 | import static org.hamcrest.MatcherAssert.assertThat;
12 |
13 | @RunWith(JUnit4.class)
14 | public class DateUtilsTest {
15 |
16 | private DateUtils subject;
17 |
18 | @Before
19 | public void setup() {
20 | subject = new DateUtils();
21 | }
22 |
23 | // Test isToday method
24 | @Test
25 | public void itReturnTrueForTodaysDate() {
26 | LocalDate today = new LocalDate();
27 | assertThat(subject.isToday(today), is(equalTo(true)));
28 | }
29 |
30 | @Test
31 | public void itReturnsFalseForDatesNotToday() {
32 | LocalDate today = new LocalDate();
33 | LocalDate yesterday = today.minusDays(1);
34 | assertThat(subject.isToday(yesterday), is(equalTo(false)));
35 |
36 | LocalDate tomorrow = today.plusDays(1);
37 | assertThat(subject.isToday(tomorrow), is(equalTo(false)));
38 | }
39 |
40 | // Test isWeek method
41 | @Test
42 | public void itReturnsTrueForDatesUpToSevenDaysFromNow() {
43 | LocalDate today = new LocalDate();
44 | LocalDate testDate;
45 | int weekDays = 7;
46 | for (int i = 0; i<=weekDays; i++) {
47 | testDate = today.plusDays(i);
48 | assertThat(subject.isThisWeek(testDate), is(equalTo(true)));
49 | }
50 | }
51 |
52 | @Test
53 | public void itReturnsFalseForYesterday() {
54 | LocalDate yesterday = new LocalDate().minusDays(1);
55 | assertThat(subject.isThisWeek(yesterday), is(equalTo(false)));
56 | }
57 |
58 | @Test
59 | public void itReturnsFalseForDatesMoreThanSevenDaysFromNow() {
60 | int weekPlusOne = 8;
61 | LocalDate date = new LocalDate().plusDays(weekPlusOne);
62 | assertThat(subject.isThisWeek(date), is(equalTo(false)));
63 | }
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/app/src/test/java/com/bignerdranch/android/testfriendlyarchitecture/viewmodel/CreateReminderViewModelTest.java:
--------------------------------------------------------------------------------
1 | package com.bignerdranch.android.testfriendlyarchitecture.viewmodel;
2 |
3 | import android.content.Context;
4 |
5 | import com.bignerdranch.android.testfriendlyarchitecture.R;
6 | import com.bignerdranch.android.testfriendlyarchitecture.model.Reminder;
7 | import com.bignerdranch.android.testfriendlyarchitecture.model.date.DateFormatter;
8 |
9 | import org.joda.time.LocalDate;
10 | import org.junit.Before;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.junit.runners.JUnit4;
14 |
15 | import static org.hamcrest.CoreMatchers.equalTo;
16 | import static org.hamcrest.CoreMatchers.is;
17 | import static org.hamcrest.MatcherAssert.assertThat;
18 | import static org.mockito.ArgumentMatchers.any;
19 | import static org.mockito.Mockito.mock;
20 | import static org.mockito.Mockito.verify;
21 | import static org.mockito.Mockito.when;
22 |
23 | @RunWith(JUnit4.class)
24 | public class CreateReminderViewModelTest {
25 | private CreateReminderViewModel subject;
26 | private Context context;
27 | private DateFormatter dateFormatter;
28 | private Reminder reminder;
29 |
30 | @Before
31 | public void setup() {
32 | context = mock(Context.class);
33 | dateFormatter = mock(DateFormatter.class);
34 | reminder = mock(Reminder.class);
35 | subject = new CreateReminderViewModel(context, dateFormatter, reminder);
36 | }
37 |
38 | @Test
39 | public void itReturnsTheTitleOfTheReminder() {
40 | String reminderTitle = "TEST_REMINDER_TITLE";
41 | when(reminder.getTitle()).thenReturn(reminderTitle);
42 |
43 | assertThat(subject.getTitle(), is(equalTo(reminderTitle)));
44 | }
45 |
46 | @Test
47 | public void itSetsTheTitleOnTheReminder() {
48 | String uiTitle = "TEST_TITLE_FROM_THE_UI";
49 | subject.setTitle(uiTitle);
50 |
51 | verify(reminder).setTitle(uiTitle);
52 | }
53 |
54 | @Test
55 | public void itUsesTheDateFormatterToFormatNonNullDate() {
56 | when(reminder.getDate()).thenReturn(new LocalDate());
57 | String dateFormatterReturnValue = "MOCK_RETURN_VALUE_FROM_DATE_FORMATTER";
58 | when(dateFormatter.format(any(LocalDate.class))).thenReturn(dateFormatterReturnValue);
59 |
60 | assertThat(subject.getDate(), is(equalTo(dateFormatterReturnValue)));
61 | }
62 |
63 | @Test
64 | public void itReturnsAStringResourceWhenGettingANullDate() {
65 | when(reminder.getDate()).thenReturn(null);
66 | String stringResourceReturnValue = "MOCK_RETURN_VALUE_FOR_CONTEXT_STRING_RESOURCE";
67 | when(context.getString(R.string.reminder_set_date)).thenReturn(stringResourceReturnValue);
68 |
69 | assertThat(subject.getDate(), is(equalTo(stringResourceReturnValue)));
70 | }
71 |
72 | @Test
73 | public void itSetsTheDateOnTheReminder() {
74 | LocalDate uiDate = new LocalDate();
75 | subject.setDate(uiDate);
76 |
77 | verify(reminder).setDate(uiDate);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.0.0-alpha8'
11 |
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BrianGardnerAtl/TestFriendlyArchitecture/c5c4d281d42825999a97a318c3d1ad4fe029a1eb/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Jul 13 15:15:00 EDT 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-milestone-1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------