├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── manuelvicnt │ │ └── com │ │ └── rxjava_android_structure │ │ ├── ApplicationTest.java │ │ └── networking │ │ └── registration │ │ └── RegistrationAPIServiceTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── accountResponse.txt │ │ ├── gamesResponse.txt │ │ ├── loginResponse.txt │ │ └── registrationResponse.txt │ ├── java │ │ └── manuelvicnt │ │ │ └── com │ │ │ └── rxjava_android_structure │ │ │ ├── HomeActivity.java │ │ │ ├── LandingActivity.java │ │ │ ├── base │ │ │ └── BaseFragment.java │ │ │ ├── data │ │ │ ├── AuthenticationManager.java │ │ │ ├── DataManager.java │ │ │ └── PrivateSharedPreferencesManager.java │ │ │ ├── home │ │ │ ├── HomeFragment.java │ │ │ └── HomeViewModel.java │ │ │ ├── login │ │ │ ├── LoginFragment.java │ │ │ └── LoginViewModel.java │ │ │ ├── model │ │ │ └── UserData.java │ │ │ ├── networking │ │ │ ├── AuthenticationRequestManager.java │ │ │ ├── UserDataRequestManager.java │ │ │ ├── account │ │ │ │ ├── AccountAPIService.java │ │ │ │ ├── AccountRequest.java │ │ │ │ ├── AccountResponse.java │ │ │ │ ├── IAccountAPI.java │ │ │ │ └── exception │ │ │ │ │ └── AccountTechFailureException.java │ │ │ ├── games │ │ │ │ ├── GamesAPIService.java │ │ │ │ ├── GamesRequest.java │ │ │ │ ├── GamesResponse.java │ │ │ │ ├── IGamesAPI.java │ │ │ │ └── exception │ │ │ │ │ └── GamesTechFailureException.java │ │ │ ├── login │ │ │ │ ├── ILoginAPI.java │ │ │ │ ├── LoginAPIService.java │ │ │ │ ├── LoginRequest.java │ │ │ │ ├── LoginResponse.java │ │ │ │ └── exception │ │ │ │ │ ├── LoginInternalException.java │ │ │ │ │ └── LoginTechFailureException.java │ │ │ ├── mock │ │ │ │ ├── MockHttpClient.java │ │ │ │ └── RestAdapterFactory.java │ │ │ ├── registration │ │ │ │ ├── IRegistrationAPI.java │ │ │ │ ├── RegistrationAPIService.java │ │ │ │ ├── RegistrationRequest.java │ │ │ │ ├── RegistrationResponse.java │ │ │ │ └── exception │ │ │ │ │ ├── RegistrationInternalException.java │ │ │ │ │ ├── RegistrationNicknameAlreadyExistsException.java │ │ │ │ │ └── RegistrationTechFailureException.java │ │ │ └── userdata │ │ │ │ └── UserDataResponse.java │ │ │ └── registration │ │ │ ├── RegistrationFragment.java │ │ │ └── RegistrationViewModel.java │ └── res │ │ ├── layout │ │ ├── activity_home.xml │ │ ├── activity_landing.xml │ │ ├── fragment_home.xml │ │ ├── fragment_login.xml │ │ └── fragment_registration.xml │ │ ├── menu │ │ └── menu_startup.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 │ └── manuelvicnt │ └── com │ └── rxjava_android_structure │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties └── settings.gradle /README.md: -------------------------------------------------------------------------------- 1 | # RxJava Android Structure 2 | 3 | Implementation of the structure explained in this blog in Medium 4 | https://medium.com/@manuelvicnt/rxjava-android-mvvm-app-structure-with-retrofit-a5605fa32c00 5 | 6 | How to use RxJava in a organised way with Retrofit as the networking library. 7 | 8 | ### Happy Path 9 | There are some things to have into account when exploring the project: 10 | - RegistrationAPIService throws more than one exception for different networking errors (all are handled in the RegistrationFragment). 11 | - RegistrationAPIService gets the status code from a failure response. 12 | - LoginAPIService gets the status code from a successful response. 13 | - After Registration, we're going to log the user in. 14 | - After Login, we're going to get the user data. 15 | - We allow Pull to refresh in the Home Activity (this means we will have to create different Subjects/Subscriber per request). 16 | - We process the user data information when both request come. If one of them fails, we don't want to update the data. 17 | 18 | ### Set up 19 | When the registration is successful, it stores a value in SharedPreferences (that's how it goes to Login or Registration). To force registration, you can uninstall the app and install it again or clear cache data in the App info (Settings > Apps > Select App). 20 | 21 | ### Unit Testing 22 | RegistrationAPIService is tested. The rest of the classes can be tested in the same way. Be aware of threading problems. 23 | 24 | ### Sad Paths 25 | You can change the response of the network request with the files stored in the assets folder. Change the 200 response for a 400, for example. That gives you the ability of exploring different sad paths. 26 | 27 | One good example would be: Change the accountResponse.txt to fail (400 response instead of 200). Run the app from registration, it will fail in the account response and the login screen will appear. 28 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'me.tatarka.retrolambda' 3 | 4 | android { 5 | compileSdkVersion 22 6 | buildToolsVersion "22.0.1" 7 | 8 | defaultConfig { 9 | applicationId "manuelvicnt.com.rxjava_android_structure" 10 | minSdkVersion 16 11 | targetSdkVersion 22 12 | versionCode 1 13 | versionName "1.0" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile fileTree(dir: 'libs', include: ['*.jar']) 25 | 26 | compile 'com.android.support:appcompat-v7:22.2.0' 27 | compile 'com.android.support:design:22.2.0' 28 | 29 | compile 'com.jakewharton:butterknife:7.0.1' 30 | compile 'com.squareup.retrofit:retrofit:1.9.0' 31 | compile 'io.reactivex:rxandroid:1.0.1' 32 | compile 'io.reactivex:rxjava:1.0.14' 33 | 34 | androidTestCompile 'org.mockito:mockito-core:1.9.5' 35 | androidTestCompile 'com.google.dexmaker:dexmaker:1.2' 36 | androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' 37 | androidTestCompile ('junit:junit:4.11') { 38 | exclude module: 'hamcrest-core' 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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/josevvivo/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/manuelvicnt/com/rxjava_android_structure/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/manuelvicnt/com/rxjava_android_structure/networking/registration/RegistrationAPIServiceTest.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.registration; 2 | 3 | import android.content.Context; 4 | import android.test.InstrumentationTestCase; 5 | 6 | import org.junit.After; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.mockito.Mock; 10 | import org.mockito.MockitoAnnotations; 11 | 12 | import java.util.ArrayList; 13 | 14 | import manuelvicnt.com.rxjava_android_structure.data.PrivateSharedPreferencesManager; 15 | import manuelvicnt.com.rxjava_android_structure.networking.registration.exception.RegistrationNicknameAlreadyExistsException; 16 | import manuelvicnt.com.rxjava_android_structure.networking.registration.exception.RegistrationTechFailureException; 17 | import retrofit.RestAdapter; 18 | import retrofit.RetrofitError; 19 | import retrofit.client.Response; 20 | import rx.Observable; 21 | import rx.observers.TestSubscriber; 22 | 23 | import static org.mockito.Matchers.any; 24 | import static org.mockito.Matchers.anyObject; 25 | import static org.mockito.Mockito.doReturn; 26 | import static org.mockito.Mockito.verify; 27 | 28 | /** 29 | * Created by ManuelVivo on 03/10/15. 30 | */ 31 | public class RegistrationAPIServiceTest extends InstrumentationTestCase { 32 | 33 | @Mock IRegistrationAPI mockRegistrationAPI; 34 | @Mock RestAdapter mockRestAdapter; 35 | @Mock PrivateSharedPreferencesManager mockPrivateSharedPreferencesManager; 36 | 37 | private RegistrationResponse registrationResponse; 38 | private RegistrationRequest registrationRequest; 39 | 40 | private RegistrationAPIService registrationAPIService; 41 | 42 | @Before 43 | protected void setUp() throws Exception { 44 | super.setUp(); 45 | 46 | MockitoAnnotations.initMocks(this); 47 | 48 | registrationResponse = new RegistrationResponse(); 49 | registrationRequest = new RegistrationRequest("", ""); 50 | registrationAPIService = new RegistrationAPIService(mockRestAdapter, mockPrivateSharedPreferencesManager); 51 | 52 | doReturn(mockRegistrationAPI).when(mockRestAdapter).create(any(Class.class)); 53 | registrationAPIService.setRegistrationAPI(mockRegistrationAPI); 54 | } 55 | 56 | @After 57 | public void tearDown() { 58 | 59 | registrationResponse = null; 60 | registrationRequest = null; 61 | registrationAPIService = null; 62 | } 63 | 64 | @Test 65 | public void testRegister_ReturnsRegistrationResponse() { 66 | 67 | doReturn(Observable.just(registrationResponse)). 68 | when(mockRegistrationAPI).register(any(RegistrationRequest.class)); 69 | 70 | TestSubscriber testSubscriber = new TestSubscriber(); 71 | 72 | registrationAPIService.register(registrationRequest) 73 | .subscribe(testSubscriber); 74 | 75 | RegistrationResponse result = (RegistrationResponse) testSubscriber.getOnNextEvents().get(0); 76 | assertEquals(registrationResponse, result); 77 | } 78 | 79 | @Test 80 | public void testRegister_SetsRequestingToTrueWhenSubscribed() { 81 | 82 | // Avoid threading problems, it finishes as soon as someone is subscribed. 83 | doReturn(Observable.never()).when(mockRegistrationAPI).register(anyObject()); 84 | 85 | registrationAPIService.register(registrationRequest).subscribe(); 86 | 87 | assertTrue(registrationAPIService.isRequestingRegistration()); 88 | } 89 | 90 | @Test 91 | public void testRegister_SetsRequestingToFalseWhenOnError() { 92 | 93 | // We have to wrap it around a try-catch since is going to throw the exception 94 | // when onError is called 95 | doReturn(Observable.error(null)).when(mockRegistrationAPI).register(anyObject()); 96 | 97 | try { 98 | registrationAPIService.register(registrationRequest).subscribe(); 99 | } catch (Exception e) { 100 | // Do Nothing 101 | } 102 | 103 | assertFalse(registrationAPIService.isRequestingRegistration()); 104 | } 105 | 106 | @Test 107 | public void testRegister_SetsRequestingToFalseWhenOnComplete() { 108 | 109 | // Avoid threading problems, it finishes as soon as someone is subscribed. 110 | doReturn(Observable.empty()).when(mockRegistrationAPI).register(anyObject()); 111 | 112 | registrationAPIService.register(registrationRequest).subscribe(); 113 | 114 | assertFalse(registrationAPIService.isRequestingRegistration()); 115 | } 116 | 117 | @Test 118 | public void testRegister_throwsRegistrationNicknameAlreadyExistsExceptionWhen401Error() { 119 | 120 | RetrofitError retrofitError = RetrofitError.httpError("", new Response("", 401, "", new ArrayList<>(), null), null, null); 121 | 122 | doReturn(Observable.error(retrofitError)).when(mockRegistrationAPI).register(anyObject()); 123 | 124 | TestSubscriber testSubscriber = new TestSubscriber(); 125 | registrationAPIService.register(registrationRequest).subscribe(testSubscriber); 126 | 127 | Throwable resultError = (Throwable) testSubscriber.getOnErrorEvents().get(0); 128 | assertTrue(resultError instanceof RegistrationNicknameAlreadyExistsException); 129 | } 130 | 131 | @Test 132 | public void testRegister_throwsRegistrationTechFailureExceptionWhenOtherError() { 133 | 134 | RetrofitError retrofitError = RetrofitError.httpError("", new Response("", 400, "", new ArrayList<>(), null), null, null); 135 | 136 | doReturn(Observable.error(retrofitError)).when(mockRegistrationAPI).register(anyObject()); 137 | 138 | TestSubscriber testSubscriber = new TestSubscriber(); 139 | registrationAPIService.register(registrationRequest).subscribe(testSubscriber); 140 | 141 | Throwable resultError = (Throwable) testSubscriber.getOnErrorEvents().get(0); 142 | assertTrue(resultError instanceof RegistrationTechFailureException); 143 | } 144 | 145 | @Test 146 | public void testRegister_storesUserNicknameOnSharedPreferencesWhenResponseSuccessful() { 147 | 148 | String expected = "nickname"; 149 | RegistrationRequest registrationRequest = new RegistrationRequest(expected, ""); 150 | 151 | doReturn(Observable.just(registrationResponse)).when(mockRegistrationAPI).register(registrationRequest); 152 | 153 | // Threading problems here. We have to make sure that it's called onNext before checking it. 154 | // One way of doing it is forcing the Observable to finish, using a testSubscriber and calling onCompleted 155 | TestSubscriber testSubscriber = new TestSubscriber(); 156 | registrationAPIService.register(registrationRequest).subscribe(testSubscriber); 157 | testSubscriber.onCompleted(); 158 | 159 | verify(mockPrivateSharedPreferencesManager).storeUserNickname(expected); 160 | } 161 | 162 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/assets/accountResponse.txt: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "Code": 200, 4 | "Message": "Success" 5 | }, 6 | "Body": { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/assets/gamesResponse.txt: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "Code": 200, 4 | "Message": "Success" 5 | }, 6 | "Body": { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/assets/loginResponse.txt: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "Code": 200, 4 | "Message": "Success" 5 | }, 6 | "Body": { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/assets/registrationResponse.txt: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "Code": 200, 4 | "Message": "Success" 5 | }, 6 | "Body": { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/HomeActivity.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.Toolbar; 6 | 7 | import butterknife.Bind; 8 | import butterknife.ButterKnife; 9 | import manuelvicnt.com.rxjava_android_structure.base.BaseFragment; 10 | import manuelvicnt.com.rxjava_android_structure.home.HomeFragment; 11 | import manuelvicnt.com.rxjava_android_structure.registration.RegistrationFragment; 12 | 13 | public class HomeActivity extends AppCompatActivity { 14 | 15 | @Bind(R.id.toolbar) Toolbar toolbar; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_landing); 22 | 23 | ButterKnife.bind(this); 24 | 25 | setSupportActionBar(toolbar); 26 | 27 | if (savedInstanceState != null) { 28 | return; 29 | } 30 | 31 | setTitle("Home"); 32 | HomeFragment homeFragment = new HomeFragment(); 33 | getSupportFragmentManager().beginTransaction() 34 | .add(R.id.fragment_container, homeFragment).commit(); 35 | } 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/LandingActivity.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.Toolbar; 7 | 8 | import butterknife.Bind; 9 | import butterknife.ButterKnife; 10 | import manuelvicnt.com.rxjava_android_structure.data.PrivateSharedPreferencesManager; 11 | import manuelvicnt.com.rxjava_android_structure.login.LoginFragment; 12 | import manuelvicnt.com.rxjava_android_structure.registration.RegistrationFragment; 13 | 14 | public class LandingActivity extends AppCompatActivity { 15 | 16 | @Bind(R.id.toolbar) Toolbar toolbar; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_landing); 23 | 24 | ButterKnife.bind(this); 25 | 26 | setSupportActionBar(toolbar); 27 | 28 | if (savedInstanceState != null) { 29 | return; 30 | } 31 | 32 | 33 | String userNickname = PrivateSharedPreferencesManager.getInstance(getApplicationContext()).getUserNickname(); 34 | Fragment initialFragment; 35 | 36 | if (userNickname == null || userNickname.isEmpty()) { 37 | 38 | setTitle("Registration"); 39 | initialFragment = new RegistrationFragment(); 40 | } else { 41 | 42 | setTitle("Login"); 43 | initialFragment = new LoginFragment(); 44 | } 45 | 46 | getSupportFragmentManager().beginTransaction() 47 | .add(R.id.fragment_container, initialFragment).commit(); 48 | 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/base/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.base; 2 | 3 | import android.app.ProgressDialog; 4 | import android.support.v4.app.Fragment; 5 | 6 | /** 7 | * Created by ManuelVivo on 03/10/15. 8 | */ 9 | public abstract class BaseFragment extends Fragment { 10 | 11 | protected ProgressDialog progressDialog; 12 | 13 | @Override 14 | public void onResume() { 15 | 16 | super.onResume(); 17 | subscribeForNetworkRequests(); 18 | } 19 | 20 | @Override 21 | public void onPause() { 22 | 23 | super.onPause(); 24 | unsubscribeFromNetworkRequests(); 25 | } 26 | 27 | protected void hideProgressDialog() { 28 | 29 | if (progressDialog != null && progressDialog.isShowing()) { 30 | progressDialog.dismiss(); 31 | } 32 | } 33 | 34 | protected abstract void subscribeForNetworkRequests(); 35 | protected abstract void unsubscribeFromNetworkRequests(); 36 | protected abstract void reconnectWithNetworkRequests(); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/data/AuthenticationManager.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.data; 2 | 3 | /** 4 | * Created by ManuelVivo on 03/10/15. 5 | */ 6 | public class AuthenticationManager { 7 | 8 | private static AuthenticationManager instance; 9 | 10 | private String nickname; 11 | private String password; 12 | private boolean userLogged; 13 | 14 | private AuthenticationManager() { 15 | 16 | } 17 | 18 | public static AuthenticationManager getInstance() { 19 | 20 | synchronized (AuthenticationManager.class) { 21 | if (instance == null) { 22 | instance = new AuthenticationManager(); 23 | } 24 | 25 | return instance; 26 | } 27 | } 28 | 29 | public String getNickname() { 30 | return nickname; 31 | } 32 | 33 | public void setNickname(String nickname) { 34 | this.nickname = nickname; 35 | } 36 | 37 | public String getPassword() { 38 | return password; 39 | } 40 | 41 | public void setPassword(String password) { 42 | this.password = password; 43 | } 44 | 45 | public void logUserIn() { 46 | userLogged = true; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/data/DataManager.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.data; 2 | 3 | import manuelvicnt.com.rxjava_android_structure.model.UserData; 4 | 5 | /** 6 | * Created by ManuelVivo on 03/10/15. 7 | */ 8 | public class DataManager { 9 | 10 | private static DataManager instance; 11 | private UserData userData; 12 | 13 | private DataManager() { 14 | 15 | userData = new UserData(); 16 | } 17 | 18 | public static DataManager getInstance() { 19 | 20 | synchronized (DataManager.class) { 21 | if (instance == null) { 22 | instance = new DataManager(); 23 | } 24 | 25 | return instance; 26 | } 27 | } 28 | 29 | public UserData getUserData() { 30 | 31 | return userData; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/data/PrivateSharedPreferencesManager.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.data; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | /** 7 | * Created by ManuelVivo on 03/10/15. 8 | */ 9 | public class PrivateSharedPreferencesManager { 10 | 11 | public static final String SHARED_PREFERENCES_KEY = "com.manuelvicnt.rxjava_android_structure"; 12 | public static final String NICKNAME_KEY = "nickname"; 13 | 14 | private static PrivateSharedPreferencesManager instance; 15 | 16 | private SharedPreferences privateSharedPreferences; 17 | 18 | private PrivateSharedPreferencesManager(Context context) { 19 | 20 | this.privateSharedPreferences = context.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE); 21 | } 22 | 23 | public static PrivateSharedPreferencesManager getInstance(Context context) { 24 | 25 | synchronized (PrivateSharedPreferencesManager.class) { 26 | if (instance == null) { 27 | instance = new PrivateSharedPreferencesManager(context); 28 | } 29 | return instance; 30 | } 31 | } 32 | 33 | private void storeStringInSharedPreferences(String key, String content) { 34 | 35 | SharedPreferences.Editor editor = privateSharedPreferences.edit(); 36 | editor.putString(key, content); 37 | editor.apply(); 38 | } 39 | 40 | private String getStringFromSharedPreferences(String key) { 41 | 42 | return privateSharedPreferences.getString(key, ""); 43 | } 44 | 45 | public void storeUserNickname(String nickname) { 46 | 47 | storeStringInSharedPreferences(NICKNAME_KEY, nickname); 48 | } 49 | 50 | public String getUserNickname() { 51 | 52 | return getStringFromSharedPreferences(NICKNAME_KEY); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/home/HomeFragment.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.home; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.widget.SwipeRefreshLayout; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | import butterknife.Bind; 11 | import butterknife.ButterKnife; 12 | import manuelvicnt.com.rxjava_android_structure.R; 13 | import manuelvicnt.com.rxjava_android_structure.base.BaseFragment; 14 | import manuelvicnt.com.rxjava_android_structure.networking.UserDataRequestManager; 15 | import rx.Subscriber; 16 | import rx.Subscription; 17 | import rx.android.schedulers.AndroidSchedulers; 18 | 19 | /** 20 | * Created by ManuelVivo on 03/10/15. 21 | */ 22 | public class HomeFragment extends BaseFragment { 23 | 24 | private HomeViewModel homeViewModel; 25 | private Subscription userDataSubscription; 26 | 27 | @Bind(R.id.user_data) TextView userDataText; 28 | @Bind(R.id.swipe_refresh) SwipeRefreshLayout swipeRefreshLayout; 29 | 30 | @Override 31 | public void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | 34 | UserDataRequestManager userDataRequestManager = 35 | UserDataRequestManager.getInstance(getActivity().getApplicationContext()); 36 | homeViewModel = new HomeViewModel(userDataRequestManager); 37 | } 38 | 39 | @Override 40 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 41 | Bundle savedInstanceState) { 42 | 43 | View rootView = inflater.inflate(R.layout.fragment_home, container, false); 44 | ButterKnife.bind(this, rootView); 45 | 46 | showRandomSuccessfulMessage(); 47 | 48 | setupRefreshLayout(); 49 | return rootView; 50 | } 51 | 52 | @Override 53 | protected void subscribeForNetworkRequests() { 54 | 55 | userDataSubscription = homeViewModel.getUserDataSubject() 56 | .observeOn(AndroidSchedulers.mainThread()) 57 | .subscribe(new UserDataSubscriber()); 58 | } 59 | 60 | @Override 61 | protected void reconnectWithNetworkRequests() { 62 | 63 | userDataSubscription = homeViewModel.createUserDataSubject() 64 | .observeOn(AndroidSchedulers.mainThread()) 65 | .subscribe(new UserDataSubscriber()); 66 | } 67 | 68 | @Override 69 | protected void unsubscribeFromNetworkRequests() { 70 | 71 | if (userDataSubscription != null) { 72 | userDataSubscription.unsubscribe(); 73 | } 74 | } 75 | 76 | private void setupRefreshLayout() { 77 | 78 | swipeRefreshLayout.setOnRefreshListener(() -> homeViewModel.getUserData()); 79 | } 80 | 81 | private void showRandomSuccessfulMessage() { 82 | 83 | showMessage(homeViewModel.generateRandomMessage()); 84 | } 85 | 86 | private void showMessage(String message) { 87 | 88 | // Because we subscribe on the Main Thread, we can do this 89 | userDataText.setText(message); 90 | } 91 | 92 | private void hideRefreshLayout() { 93 | 94 | swipeRefreshLayout.setRefreshing(false); 95 | } 96 | 97 | private class UserDataSubscriber extends Subscriber { 98 | 99 | @Override 100 | public void onCompleted() { 101 | 102 | hideRefreshLayout(); 103 | 104 | // To be able to pull to refresh (and make another request), 105 | // we have to reset the Subject in the VM 106 | reconnectWithNetworkRequests(); 107 | } 108 | 109 | @Override 110 | public void onError(Throwable e) { 111 | 112 | hideRefreshLayout(); 113 | 114 | // To be able to make another UserData request when it fails, 115 | // we have to reset the Subject in the VM 116 | reconnectWithNetworkRequests(); 117 | 118 | showMessage("Error"); 119 | } 120 | 121 | @Override 122 | public void onNext(Object userDataResponse) { 123 | 124 | showRandomSuccessfulMessage(); 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/home/HomeViewModel.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.home; 2 | 3 | import java.util.Random; 4 | 5 | import manuelvicnt.com.rxjava_android_structure.networking.UserDataRequestManager; 6 | import rx.subjects.AsyncSubject; 7 | 8 | /** 9 | * Created by ManuelVivo on 03/10/15. 10 | */ 11 | public class HomeViewModel { 12 | 13 | private UserDataRequestManager userDataRequestManager; 14 | private AsyncSubject userDataSubject; 15 | private Random random; 16 | 17 | public HomeViewModel(UserDataRequestManager userDataRequestManager) { 18 | 19 | this.userDataRequestManager = userDataRequestManager; 20 | userDataSubject = AsyncSubject.create(); 21 | random = new Random(); 22 | } 23 | 24 | public AsyncSubject createUserDataSubject() { 25 | 26 | userDataSubject = AsyncSubject.create(); 27 | return userDataSubject; 28 | } 29 | 30 | public AsyncSubject getUserDataSubject() { 31 | 32 | return userDataSubject; 33 | } 34 | 35 | public void getUserData() { 36 | 37 | userDataRequestManager.getUserData() 38 | .subscribe(userDataSubject); 39 | } 40 | 41 | public String generateRandomMessage() { 42 | 43 | double nextRandom = random.nextDouble(); 44 | return Double.toString(nextRandom); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/login/LoginFragment.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.login; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.Toast; 10 | 11 | import butterknife.ButterKnife; 12 | import butterknife.OnClick; 13 | import manuelvicnt.com.rxjava_android_structure.HomeActivity; 14 | import manuelvicnt.com.rxjava_android_structure.R; 15 | import manuelvicnt.com.rxjava_android_structure.base.BaseFragment; 16 | import manuelvicnt.com.rxjava_android_structure.data.AuthenticationManager; 17 | import manuelvicnt.com.rxjava_android_structure.networking.AuthenticationRequestManager; 18 | import manuelvicnt.com.rxjava_android_structure.networking.account.exception.AccountTechFailureException; 19 | import manuelvicnt.com.rxjava_android_structure.networking.games.exception.GamesTechFailureException; 20 | import manuelvicnt.com.rxjava_android_structure.networking.login.LoginResponse; 21 | import manuelvicnt.com.rxjava_android_structure.networking.login.exception.LoginInternalException; 22 | import manuelvicnt.com.rxjava_android_structure.networking.login.exception.LoginTechFailureException; 23 | import rx.Subscriber; 24 | import rx.Subscription; 25 | import rx.android.schedulers.AndroidSchedulers; 26 | 27 | /** 28 | * Created by ManuelVivo on 03/10/15. 29 | */ 30 | public class LoginFragment extends BaseFragment { 31 | 32 | private LoginViewModel loginViewModel; 33 | private Subscription loginSubscription; 34 | 35 | @Override 36 | public void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | 39 | AuthenticationRequestManager authenticationRequestManager = 40 | AuthenticationRequestManager.getInstance(getActivity().getApplicationContext()); 41 | loginViewModel = new LoginViewModel(authenticationRequestManager); 42 | } 43 | 44 | @Override 45 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 46 | Bundle savedInstanceState) { 47 | 48 | View rootView = inflater.inflate(R.layout.fragment_login, container, false); 49 | ButterKnife.bind(this, rootView); 50 | 51 | return rootView; 52 | } 53 | 54 | @Override 55 | protected void subscribeForNetworkRequests() { 56 | 57 | loginSubscription = loginViewModel.getLoginSubject() 58 | .observeOn(AndroidSchedulers.mainThread()) 59 | .subscribe(new LoginSubscriber()); 60 | } 61 | 62 | @Override 63 | protected void reconnectWithNetworkRequests() { 64 | 65 | loginSubscription = loginViewModel.createLoginSubject() 66 | .observeOn(AndroidSchedulers.mainThread()) 67 | .subscribe(new LoginSubscriber()); 68 | } 69 | 70 | @Override 71 | protected void unsubscribeFromNetworkRequests() { 72 | 73 | if (loginSubscription != null) { 74 | loginSubscription.unsubscribe(); 75 | } 76 | } 77 | 78 | @OnClick(R.id.login) 79 | public void loginButtonTap(View view) { 80 | 81 | AuthenticationManager.getInstance().setPassword("password"); 82 | loginViewModel.login(); 83 | 84 | progressDialog = ProgressDialog.show(getActivity(), "Login", "...", true); 85 | } 86 | 87 | private void showMessage(String message) { 88 | 89 | Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show(); 90 | } 91 | 92 | private void launchHomeActivity() { 93 | 94 | Intent intent = new Intent(getActivity(), HomeActivity.class); 95 | startActivity(intent); 96 | getActivity().finish(); 97 | } 98 | 99 | private class LoginSubscriber extends Subscriber { 100 | 101 | @Override 102 | public void onCompleted() { 103 | 104 | hideProgressDialog(); 105 | launchHomeActivity(); 106 | } 107 | 108 | @Override 109 | public void onError(Throwable e) { 110 | 111 | hideProgressDialog(); 112 | 113 | // To be able to make another Login request, 114 | // we have to reset the Subject in the VM 115 | reconnectWithNetworkRequests(); 116 | 117 | if (e instanceof LoginInternalException 118 | || e instanceof LoginTechFailureException) { 119 | 120 | showMessage("Login Failure"); 121 | 122 | } else if (e instanceof AccountTechFailureException) { 123 | 124 | showMessage("Account failed"); 125 | launchHomeActivity(); 126 | } else if (e instanceof GamesTechFailureException) { 127 | 128 | showMessage("Games failed"); 129 | launchHomeActivity(); 130 | } 131 | } 132 | 133 | @Override 134 | public void onNext(Object userDataResponse) { 135 | 136 | } 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/login/LoginViewModel.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.login; 2 | 3 | import manuelvicnt.com.rxjava_android_structure.networking.AuthenticationRequestManager; 4 | import manuelvicnt.com.rxjava_android_structure.networking.login.LoginResponse; 5 | import rx.subjects.AsyncSubject; 6 | 7 | /** 8 | * Created by ManuelVivo on 03/10/15. 9 | */ 10 | public class LoginViewModel { 11 | 12 | private AuthenticationRequestManager authenticationRequestManager; 13 | private AsyncSubject loginSubject; 14 | 15 | public LoginViewModel(AuthenticationRequestManager authenticationRequestManager) { 16 | 17 | this.authenticationRequestManager = authenticationRequestManager; 18 | loginSubject = AsyncSubject.create(); 19 | } 20 | 21 | public AsyncSubject createLoginSubject() { 22 | 23 | loginSubject = AsyncSubject.create(); 24 | return loginSubject; 25 | } 26 | 27 | public AsyncSubject getLoginSubject() { 28 | 29 | return loginSubject; 30 | } 31 | 32 | public void login() { 33 | 34 | authenticationRequestManager.login() 35 | .subscribe(loginSubject); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/model/UserData.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.model; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by ManuelVivo on 03/10/15. 7 | */ 8 | public class UserData { 9 | 10 | private List games; 11 | private String accountInformation; 12 | 13 | public UserData() { 14 | } 15 | 16 | public List getGames() { 17 | return games; 18 | } 19 | 20 | public void setGames(List games) { 21 | this.games = games; 22 | } 23 | 24 | public String getAccountInformation() { 25 | return accountInformation; 26 | } 27 | 28 | public void setAccountInformation(String accountInformation) { 29 | this.accountInformation = accountInformation; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/AuthenticationRequestManager.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking; 2 | 3 | import android.content.Context; 4 | 5 | import manuelvicnt.com.rxjava_android_structure.data.AuthenticationManager; 6 | import manuelvicnt.com.rxjava_android_structure.data.PrivateSharedPreferencesManager; 7 | import manuelvicnt.com.rxjava_android_structure.networking.login.LoginAPIService; 8 | import manuelvicnt.com.rxjava_android_structure.networking.login.LoginRequest; 9 | import manuelvicnt.com.rxjava_android_structure.networking.login.LoginResponse; 10 | import manuelvicnt.com.rxjava_android_structure.networking.login.exception.LoginInternalException; 11 | import manuelvicnt.com.rxjava_android_structure.networking.mock.RestAdapterFactory; 12 | import manuelvicnt.com.rxjava_android_structure.networking.registration.RegistrationAPIService; 13 | import manuelvicnt.com.rxjava_android_structure.networking.registration.RegistrationRequest; 14 | import manuelvicnt.com.rxjava_android_structure.networking.registration.RegistrationResponse; 15 | import manuelvicnt.com.rxjava_android_structure.networking.registration.exception.RegistrationInternalException; 16 | import retrofit.RestAdapter; 17 | import rx.Observable; 18 | 19 | /** 20 | * Created by ManuelVivo on 03/10/15. 21 | */ 22 | public class AuthenticationRequestManager { 23 | 24 | private static AuthenticationRequestManager instance; 25 | 26 | private AuthenticationManager authenticationManager; 27 | private PrivateSharedPreferencesManager privateSharedPreferencesManager; 28 | 29 | private RegistrationAPIService registrationAPIService; 30 | private LoginAPIService loginAPIService; 31 | private UserDataRequestManager userDataRequestManager; 32 | 33 | private AuthenticationRequestManager(Context context) { 34 | 35 | this.authenticationManager = AuthenticationManager.getInstance(); 36 | 37 | privateSharedPreferencesManager = PrivateSharedPreferencesManager.getInstance(context); 38 | RestAdapter restAdapter = RestAdapterFactory.getAdapter(context); 39 | 40 | this.registrationAPIService = new RegistrationAPIService(restAdapter, privateSharedPreferencesManager); 41 | this.loginAPIService = new LoginAPIService(restAdapter, authenticationManager); 42 | 43 | this.userDataRequestManager = UserDataRequestManager.getInstance(context); 44 | } 45 | 46 | public static AuthenticationRequestManager getInstance(Context context) { 47 | 48 | synchronized (AuthenticationRequestManager.class) { 49 | if (instance == null) { 50 | instance = new AuthenticationRequestManager(context); 51 | } 52 | 53 | return instance; 54 | } 55 | } 56 | 57 | public Observable register() { 58 | 59 | return registrationAPIService.register(createBodyForRegistration()) 60 | .flatMap(this::makeLoginRequest); 61 | } 62 | 63 | public Observable login() { 64 | 65 | return loginAPIService.login(createLoginRequest()) 66 | .flatMap(this::makeGetUserDataRequest); 67 | } 68 | 69 | private Observable makeLoginRequest(RegistrationResponse registrationResponse) { 70 | 71 | return login(); 72 | } 73 | 74 | private Observable makeGetUserDataRequest(LoginResponse loginResponse) { 75 | 76 | return userDataRequestManager.getUserData(); 77 | } 78 | 79 | private LoginRequest createLoginRequest() { 80 | 81 | String nickname = privateSharedPreferencesManager.getUserNickname(); 82 | String password = authenticationManager.getPassword(); 83 | 84 | if (nickname == null || nickname.isEmpty() || 85 | password == null || password.isEmpty()) { 86 | throw new LoginInternalException(); 87 | } 88 | 89 | return new LoginRequest(nickname, password); 90 | } 91 | 92 | private RegistrationRequest createBodyForRegistration() { 93 | 94 | String nickname = authenticationManager.getNickname(); 95 | String password = authenticationManager.getPassword(); 96 | 97 | if (nickname == null || nickname.isEmpty() || 98 | password == null || password.isEmpty()) { 99 | throw new RegistrationInternalException(); 100 | } 101 | 102 | return new RegistrationRequest(nickname, password); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/UserDataRequestManager.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.Collections; 6 | 7 | import manuelvicnt.com.rxjava_android_structure.data.DataManager; 8 | import manuelvicnt.com.rxjava_android_structure.model.UserData; 9 | import manuelvicnt.com.rxjava_android_structure.networking.account.AccountAPIService; 10 | import manuelvicnt.com.rxjava_android_structure.networking.account.AccountRequest; 11 | import manuelvicnt.com.rxjava_android_structure.networking.account.AccountResponse; 12 | import manuelvicnt.com.rxjava_android_structure.networking.games.GamesAPIService; 13 | import manuelvicnt.com.rxjava_android_structure.networking.games.GamesRequest; 14 | import manuelvicnt.com.rxjava_android_structure.networking.games.GamesResponse; 15 | import manuelvicnt.com.rxjava_android_structure.networking.mock.RestAdapterFactory; 16 | import retrofit.RestAdapter; 17 | import rx.Observable; 18 | 19 | /** 20 | * Created by ManuelVivo on 03/10/15. 21 | */ 22 | public class UserDataRequestManager { 23 | 24 | private static UserDataRequestManager instance; 25 | 26 | private DataManager dataManager; 27 | 28 | private AccountAPIService accountAPIService; 29 | private GamesAPIService gamesAPIService; 30 | 31 | private UserDataRequestManager(Context context) { 32 | 33 | dataManager = DataManager.getInstance(); 34 | 35 | RestAdapter restAdapter = RestAdapterFactory.getAdapter(context); 36 | accountAPIService = new AccountAPIService(restAdapter); 37 | gamesAPIService = new GamesAPIService(restAdapter); 38 | } 39 | 40 | public static UserDataRequestManager getInstance(Context context) { 41 | 42 | synchronized (UserDataRequestManager.class) { 43 | if (instance == null) { 44 | instance = new UserDataRequestManager(context); 45 | } 46 | 47 | return instance; 48 | } 49 | } 50 | 51 | public Observable getUserData() { 52 | 53 | return Observable.zip( 54 | getAccount(), 55 | getGames(), 56 | this::processUserDataResult); 57 | } 58 | 59 | private Object processUserDataResult(AccountResponse accountResponse, GamesResponse gamesResponse) { 60 | 61 | UserData userData = dataManager.getUserData(); 62 | // TODO: do this for real 63 | userData.setAccountInformation("get information from the account response"); 64 | userData.setGames(Collections.EMPTY_LIST); 65 | 66 | return Observable.just(new Object()); 67 | } 68 | 69 | private Observable getAccount() { 70 | 71 | return accountAPIService.getAccount(createAccountRequest()); 72 | } 73 | 74 | private Observable getGames() { 75 | 76 | return gamesAPIService.getGames(createGamesRequest()); 77 | } 78 | 79 | private GamesRequest createGamesRequest() { 80 | 81 | return new GamesRequest("nickname"); 82 | } 83 | 84 | private AccountRequest createAccountRequest() { 85 | 86 | return new AccountRequest("nickname"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/account/AccountAPIService.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.account; 2 | 3 | import manuelvicnt.com.rxjava_android_structure.networking.account.exception.AccountTechFailureException; 4 | import retrofit.RestAdapter; 5 | import rx.Observable; 6 | 7 | /** 8 | * Created by ManuelVivo on 03/10/15. 9 | */ 10 | public class AccountAPIService { 11 | 12 | private IAccountAPI accountAPI; 13 | private boolean isRequestingAccount; 14 | 15 | public AccountAPIService(RestAdapter restAdapter) { 16 | 17 | this.accountAPI = restAdapter.create(IAccountAPI.class); 18 | } 19 | 20 | public boolean isRequestingAccount() { 21 | return isRequestingAccount; 22 | } 23 | 24 | public Observable getAccount(AccountRequest request) { 25 | 26 | return accountAPI.getAccountInformation(request.getNickname()) 27 | .doOnSubscribe(() -> isRequestingAccount = true) 28 | .doOnTerminate(() -> isRequestingAccount = false) 29 | .doOnError(this::handleAccountError); 30 | } 31 | 32 | private void handleAccountError(Throwable throwable) { 33 | 34 | throw new AccountTechFailureException(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/account/AccountRequest.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.account; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by ManuelVivo on 03/10/15. 7 | */ 8 | public class AccountRequest { 9 | 10 | @SerializedName("nickname") private String nickname; 11 | 12 | public AccountRequest(String nickname) { 13 | this.nickname = nickname; 14 | } 15 | 16 | public String getNickname() { 17 | return nickname; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/account/AccountResponse.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.account; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by ManuelVivo on 03/10/15. 7 | */ 8 | public class AccountResponse { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/account/IAccountAPI.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.account; 2 | 3 | import retrofit.http.GET; 4 | import retrofit.http.Header; 5 | import rx.Observable; 6 | 7 | /** 8 | * Created by ManuelVivo on 03/10/15. 9 | */ 10 | public interface IAccountAPI { 11 | 12 | @GET("/account") 13 | Observable getAccountInformation(@Header("nickname") String nickname); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/account/exception/AccountTechFailureException.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.account.exception; 2 | 3 | /** 4 | * Created by ManuelVivo on 03/10/15. 5 | */ 6 | public class AccountTechFailureException extends IllegalArgumentException { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/games/GamesAPIService.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.games; 2 | 3 | import manuelvicnt.com.rxjava_android_structure.networking.account.AccountRequest; 4 | import manuelvicnt.com.rxjava_android_structure.networking.account.AccountResponse; 5 | import manuelvicnt.com.rxjava_android_structure.networking.account.IAccountAPI; 6 | import manuelvicnt.com.rxjava_android_structure.networking.games.exception.GamesTechFailureException; 7 | import manuelvicnt.com.rxjava_android_structure.networking.login.exception.LoginTechFailureException; 8 | import retrofit.RestAdapter; 9 | import rx.Observable; 10 | 11 | /** 12 | * Created by ManuelVivo on 03/10/15. 13 | */ 14 | public class GamesAPIService { 15 | 16 | private IGamesAPI gamesAPI; 17 | private boolean isRequestingGames; 18 | 19 | public GamesAPIService(RestAdapter restAdapter) { 20 | 21 | this.gamesAPI = restAdapter.create(IGamesAPI.class); 22 | } 23 | 24 | public boolean isRequestingGames() { 25 | return isRequestingGames; 26 | } 27 | 28 | public Observable getGames(GamesRequest request) { 29 | 30 | return gamesAPI.getGamesInformation(request.getNickname()) 31 | .doOnSubscribe(() -> isRequestingGames = true) 32 | .doOnTerminate(() -> isRequestingGames = false) 33 | .doOnError(this::handleAccountError); 34 | } 35 | 36 | private void handleAccountError(Throwable throwable) { 37 | 38 | throw new GamesTechFailureException(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/games/GamesRequest.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.games; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by ManuelVivo on 03/10/15. 7 | */ 8 | public class GamesRequest { 9 | 10 | @SerializedName("nickname") private String nickname; 11 | 12 | public GamesRequest(String nickname) { 13 | this.nickname = nickname; 14 | } 15 | 16 | public String getNickname() { 17 | return nickname; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/games/GamesResponse.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.games; 2 | 3 | /** 4 | * Created by ManuelVivo on 03/10/15. 5 | */ 6 | public class GamesResponse { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/games/IGamesAPI.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.games; 2 | 3 | import retrofit.http.GET; 4 | import retrofit.http.Header; 5 | import rx.Observable; 6 | 7 | /** 8 | * Created by ManuelVivo on 03/10/15. 9 | */ 10 | public interface IGamesAPI { 11 | 12 | @GET("/games") 13 | Observable getGamesInformation(@Header("nickname") String nickname); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/games/exception/GamesTechFailureException.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.games.exception; 2 | 3 | /** 4 | * Created by ManuelVivo on 03/10/15. 5 | */ 6 | public class GamesTechFailureException extends IllegalArgumentException { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/login/ILoginAPI.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.login; 2 | 3 | import manuelvicnt.com.rxjava_android_structure.networking.registration.RegistrationRequest; 4 | import manuelvicnt.com.rxjava_android_structure.networking.registration.RegistrationResponse; 5 | import retrofit.client.Response; 6 | import retrofit.http.Body; 7 | import retrofit.http.GET; 8 | import retrofit.http.Header; 9 | import retrofit.http.POST; 10 | import rx.Observable; 11 | 12 | /** 13 | * Created by ManuelVivo on 03/10/15. 14 | */ 15 | public interface ILoginAPI { 16 | 17 | @GET("/login") 18 | Observable login(@Header("nickname") String nickname, @Header("password") String password); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/login/LoginAPIService.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.login; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | 10 | import manuelvicnt.com.rxjava_android_structure.data.AuthenticationManager; 11 | import manuelvicnt.com.rxjava_android_structure.networking.login.exception.LoginTechFailureException; 12 | import retrofit.RestAdapter; 13 | import retrofit.client.Response; 14 | import rx.Observable; 15 | 16 | /** 17 | * Created by ManuelVivo on 03/10/15. 18 | */ 19 | public class LoginAPIService { 20 | 21 | private ILoginAPI loginAPI; 22 | private boolean isRequestingLogin; 23 | private AuthenticationManager authenticationManager; 24 | 25 | public LoginAPIService(RestAdapter restAdapter, AuthenticationManager authenticationManager) { 26 | 27 | this.loginAPI = restAdapter.create(ILoginAPI.class); 28 | this.authenticationManager = authenticationManager; 29 | } 30 | 31 | public boolean isRequestingLogin() { 32 | return isRequestingLogin; 33 | } 34 | 35 | public Observable login(LoginRequest request) { 36 | 37 | return loginAPI.login(request.getNickname(), request.getPassword()) 38 | .doOnSubscribe(() -> isRequestingLogin = true) 39 | .doOnTerminate(() -> isRequestingLogin = false) 40 | .doOnError(this::handleLoginError) 41 | .flatMap(this::parseLoginResponse) 42 | .doOnNext(this::processLoginResponse); 43 | } 44 | 45 | private void handleLoginError(Throwable throwable) { 46 | 47 | throw new LoginTechFailureException(); 48 | } 49 | 50 | private void processLoginResponse(LoginResponse loginResponse) { 51 | 52 | authenticationManager.logUserIn(); 53 | } 54 | 55 | private Observable parseLoginResponse(Response response) { 56 | 57 | try { 58 | String body = fromStream(response.getBody().in()); 59 | Gson gson = new Gson(); 60 | 61 | LoginResponse loginResponse = gson.fromJson(body, LoginResponse.class); 62 | loginResponse.setLoginStatusResponse(response.getStatus()); 63 | return Observable.just(loginResponse); 64 | 65 | } catch (IOException e) { 66 | return null; 67 | } 68 | } 69 | 70 | private String fromStream(InputStream inputStream) throws IOException { 71 | 72 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 73 | StringBuilder out = new StringBuilder(); 74 | String newLine = System.getProperty("line.separator"); 75 | 76 | String line; 77 | while((line = reader.readLine()) != null) { 78 | out.append(line); 79 | out.append(newLine); 80 | } 81 | 82 | return out.toString(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/login/LoginRequest.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.login; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by ManuelVivo on 03/10/15. 7 | */ 8 | public class LoginRequest { 9 | 10 | @SerializedName("nickname") private String nickname; 11 | @SerializedName("password") private String password; 12 | 13 | public LoginRequest(String nickname, String password) { 14 | this.nickname = nickname; 15 | this.password = password; 16 | } 17 | 18 | public String getNickname() { 19 | return nickname; 20 | } 21 | 22 | public String getPassword() { 23 | return password; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/login/LoginResponse.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.login; 2 | 3 | /** 4 | * Created by ManuelVivo on 03/10/15. 5 | */ 6 | public class LoginResponse { 7 | 8 | private int loginStatusResponse; 9 | 10 | public LoginResponse() { 11 | 12 | } 13 | 14 | public int getLoginStatusResponse() { 15 | return loginStatusResponse; 16 | } 17 | 18 | public void setLoginStatusResponse(int loginStatusResponse) { 19 | this.loginStatusResponse = loginStatusResponse; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/login/exception/LoginInternalException.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.login.exception; 2 | 3 | /** 4 | * Created by ManuelVivo on 03/10/15. 5 | */ 6 | public class LoginInternalException extends IllegalArgumentException { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/login/exception/LoginTechFailureException.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.login.exception; 2 | 3 | /** 4 | * Created by ManuelVivo on 03/10/15. 5 | */ 6 | public class LoginTechFailureException extends IllegalArgumentException { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/mock/MockHttpClient.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.mock; 2 | 3 | 4 | import android.content.Context; 5 | import android.preference.PreferenceActivity; 6 | import android.util.Log; 7 | 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.InputStreamReader; 15 | import java.io.Reader; 16 | import java.io.StringWriter; 17 | import java.io.Writer; 18 | import java.util.ArrayList; 19 | import java.util.Collections; 20 | import java.util.Iterator; 21 | import java.util.List; 22 | 23 | import retrofit.client.Client; 24 | import retrofit.client.Header; 25 | import retrofit.client.Request; 26 | import retrofit.client.Response; 27 | import retrofit.mime.TypedByteArray; 28 | 29 | /** 30 | * Created by ManuelVivo on 03/10/15. 31 | */ 32 | public class MockHttpClient implements Client { 33 | 34 | private Context context; 35 | private List
headerList; 36 | 37 | public MockHttpClient(Context context) { 38 | 39 | this.context = context; 40 | headerList = new ArrayList<>(); 41 | } 42 | 43 | @Override 44 | public Response execute(Request request) throws IOException { 45 | 46 | try { 47 | 48 | String url = request.getUrl(); 49 | 50 | MockResponse mockResponse = null; 51 | 52 | if (url.contains("registration")) { 53 | mockResponse = getResponse("registrationResponse.txt"); 54 | } else if (url.contains("login")) { 55 | mockResponse = getResponse("loginResponse.txt"); 56 | } else if (url.contains("account")) { 57 | mockResponse = getResponse("accountResponse.txt"); 58 | } else if (url.contains("games")) { 59 | mockResponse = getResponse("gamesResponse.txt"); 60 | } 61 | 62 | TypedByteArray responseBody = new TypedByteArray("application/json", mockResponse.getResponseBody().getBytes()); 63 | 64 | Thread.sleep(2000); 65 | 66 | return new Response(request.getUrl(), mockResponse.getResponseCode(), mockResponse.getResponseMessage(), headerList, responseBody); 67 | 68 | } catch (Exception e) { 69 | 70 | TypedByteArray responseBody = new TypedByteArray("application/json", "".getBytes()); 71 | 72 | return new Response(request.getUrl(), 500, "File not found", Collections.EMPTY_LIST, responseBody); 73 | } 74 | } 75 | 76 | public MockResponse getResponse(String path) throws Exception { 77 | 78 | InputStream inputStream = context.getAssets().open(path); 79 | String result = convertStreamToString(inputStream); 80 | 81 | Log.d("TEST", result); 82 | String responseBody = getResponseBody(result); 83 | 84 | Log.d("TEST", responseBody); 85 | JSONObject responseHeader = getResponseHeader(result); 86 | processResponseHeader(result); 87 | String responseCode = getResponseCode(responseHeader); 88 | String responseMessage = getResponseMessage(responseHeader); 89 | 90 | return new MockResponse(responseCode, responseMessage, responseBody); 91 | } 92 | 93 | private String getResponseBody(String responseJson) throws JSONException { 94 | 95 | JSONObject topLevelJsonObject = new JSONObject(responseJson); 96 | return topLevelJsonObject.getJSONObject("Body").toString(); 97 | } 98 | 99 | private String getResponseCode(JSONObject responseHeaderJson) throws JSONException { 100 | 101 | return responseHeaderJson.getString("Code"); 102 | } 103 | 104 | private String getResponseMessage(JSONObject responseHeaderJson) throws JSONException { 105 | 106 | return responseHeaderJson.getString("Message"); 107 | } 108 | 109 | private JSONObject getResponseHeader(String responseJson) throws JSONException { 110 | 111 | JSONObject topLevelObject = new JSONObject(responseJson); 112 | return topLevelObject.getJSONObject("Header"); 113 | } 114 | 115 | private void processResponseHeader(String responseHeader) throws JSONException { 116 | 117 | headerList.clear(); 118 | JSONObject topLevelObject = new JSONObject(responseHeader); 119 | 120 | JSONObject responseHeaderJson = topLevelObject.getJSONObject("Header"); 121 | Iterator keys = responseHeaderJson.keys(); 122 | 123 | while (keys.hasNext()) { 124 | String key = (String) keys.next(); 125 | Header header = new Header(key, responseHeaderJson.getString(key)); 126 | headerList.add(header); 127 | } 128 | } 129 | 130 | private static String convertStreamToString(InputStream inputStream) 131 | throws IOException { 132 | Writer writer = new StringWriter(); 133 | char[] buffer = new char[2048]; 134 | try { 135 | Reader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); 136 | 137 | int n; 138 | while ((n = reader.read(buffer)) != -1) { 139 | writer.write(buffer, 0, n); 140 | } 141 | } finally { 142 | inputStream.close(); 143 | } 144 | String text = writer.toString(); 145 | return text; 146 | } 147 | 148 | 149 | public static class MockResponse { 150 | 151 | private String responseBody; 152 | private String responseCodeString; 153 | private String responseMessage; 154 | 155 | public MockResponse(String responseCode, String responseMessage, String responseBody) { 156 | 157 | this.responseCodeString = responseCode; 158 | this.responseMessage = responseMessage; 159 | this.responseBody = responseBody; 160 | } 161 | 162 | public int getResponseCode() { 163 | 164 | return Integer.valueOf(responseCodeString); 165 | } 166 | 167 | public String getResponseMessage() { 168 | return responseMessage; 169 | } 170 | 171 | public String getResponseBody() { 172 | return responseBody; 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/mock/RestAdapterFactory.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.mock; 2 | 3 | import android.content.Context; 4 | 5 | import manuelvicnt.com.rxjava_android_structure.networking.mock.MockHttpClient; 6 | import retrofit.RestAdapter; 7 | 8 | /** 9 | * Created by ManuelVivo on 03/10/15. 10 | */ 11 | public class RestAdapterFactory { 12 | 13 | public static RestAdapter getAdapter(Context context) { 14 | 15 | return createAdapter(context); 16 | } 17 | 18 | private static RestAdapter createAdapter(Context context) { 19 | 20 | RestAdapter restAdapter = new RestAdapter.Builder() 21 | .setClient(new MockHttpClient(context)) 22 | .setEndpoint("mock") 23 | .setLogLevel(RestAdapter.LogLevel.FULL) 24 | .build(); 25 | 26 | return restAdapter; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/registration/IRegistrationAPI.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.registration; 2 | 3 | import retrofit.http.Body; 4 | import retrofit.http.GET; 5 | import retrofit.http.POST; 6 | import rx.Observable; 7 | 8 | /** 9 | * Created by ManuelVivo on 03/10/15. 10 | */ 11 | public interface IRegistrationAPI { 12 | 13 | @POST("/registration") 14 | Observable register(@Body RegistrationRequest request); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/registration/RegistrationAPIService.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.registration; 2 | 3 | import manuelvicnt.com.rxjava_android_structure.data.PrivateSharedPreferencesManager; 4 | import manuelvicnt.com.rxjava_android_structure.networking.registration.exception.RegistrationNicknameAlreadyExistsException; 5 | import manuelvicnt.com.rxjava_android_structure.networking.registration.exception.RegistrationTechFailureException; 6 | import retrofit.RestAdapter; 7 | import retrofit.RetrofitError; 8 | import rx.Observable; 9 | 10 | /** 11 | * Created by ManuelVivo on 03/10/15. 12 | */ 13 | public class RegistrationAPIService { 14 | 15 | private IRegistrationAPI registrationAPI; 16 | private PrivateSharedPreferencesManager privateSharedPreferencesManager; 17 | private boolean isRequestingRegistration; 18 | 19 | public RegistrationAPIService(RestAdapter restAdapter, PrivateSharedPreferencesManager privateSharedPreferencesManager) { 20 | 21 | this.registrationAPI = restAdapter.create(IRegistrationAPI.class); 22 | this.privateSharedPreferencesManager = privateSharedPreferencesManager; 23 | } 24 | 25 | public boolean isRequestingRegistration() { 26 | return isRequestingRegistration; 27 | } 28 | 29 | public void setRegistrationAPI(IRegistrationAPI registrationAPI) { 30 | this.registrationAPI = registrationAPI; 31 | } 32 | 33 | public Observable register(RegistrationRequest request) { 34 | 35 | return registrationAPI.register(request) 36 | .doOnSubscribe(() -> isRequestingRegistration = true) 37 | .doOnTerminate(() -> isRequestingRegistration = false) 38 | .doOnError(this::handleRegistrationError) 39 | .doOnNext(registrationResponse -> processRegistrationResponse(request, registrationResponse)); 40 | } 41 | 42 | private void handleRegistrationError(Throwable throwable) { 43 | 44 | if (throwable instanceof RetrofitError) { 45 | 46 | int status = ((RetrofitError) throwable).getResponse().getStatus(); 47 | 48 | if (status == 401) { 49 | throw new RegistrationNicknameAlreadyExistsException(); 50 | } else { 51 | throw new RegistrationTechFailureException(); 52 | } 53 | 54 | } else { 55 | throw new RegistrationTechFailureException(); 56 | } 57 | } 58 | 59 | private void processRegistrationResponse(RegistrationRequest registrationRequest, RegistrationResponse registrationResponse) { 60 | 61 | privateSharedPreferencesManager.storeUserNickname(registrationRequest.getNickname()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/registration/RegistrationRequest.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.registration; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by ManuelVivo on 03/10/15. 7 | */ 8 | public class RegistrationRequest { 9 | 10 | @SerializedName("nickname") private String nickname; 11 | @SerializedName("password") private String password; 12 | 13 | public RegistrationRequest(String nickname, String password) { 14 | this.nickname = nickname; 15 | this.password = password; 16 | } 17 | 18 | public String getNickname() { 19 | return nickname; 20 | } 21 | 22 | public String getPassword() { 23 | return password; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/registration/RegistrationResponse.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.registration; 2 | 3 | /** 4 | * Created by ManuelVivo on 03/10/15. 5 | */ 6 | public class RegistrationResponse { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/registration/exception/RegistrationInternalException.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.registration.exception; 2 | 3 | /** 4 | * Created by ManuelVivo on 03/10/15. 5 | */ 6 | public class RegistrationInternalException extends IllegalArgumentException { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/registration/exception/RegistrationNicknameAlreadyExistsException.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.registration.exception; 2 | 3 | /** 4 | * Created by ManuelVivo on 03/10/15. 5 | */ 6 | public class RegistrationNicknameAlreadyExistsException extends IllegalArgumentException { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/registration/exception/RegistrationTechFailureException.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.registration.exception; 2 | 3 | /** 4 | * Created by ManuelVivo on 03/10/15. 5 | */ 6 | public class RegistrationTechFailureException extends IllegalArgumentException { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/networking/userdata/UserDataResponse.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.networking.userdata; 2 | 3 | /** 4 | * Created by ManuelVivo on 03/10/15. 5 | */ 6 | public class UserDataResponse { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/registration/RegistrationFragment.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.registration; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v4.app.FragmentManager; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.Toast; 11 | 12 | import butterknife.ButterKnife; 13 | import butterknife.OnClick; 14 | import manuelvicnt.com.rxjava_android_structure.HomeActivity; 15 | import manuelvicnt.com.rxjava_android_structure.R; 16 | import manuelvicnt.com.rxjava_android_structure.base.BaseFragment; 17 | import manuelvicnt.com.rxjava_android_structure.data.AuthenticationManager; 18 | import manuelvicnt.com.rxjava_android_structure.login.LoginFragment; 19 | import manuelvicnt.com.rxjava_android_structure.networking.AuthenticationRequestManager; 20 | import manuelvicnt.com.rxjava_android_structure.networking.account.exception.AccountTechFailureException; 21 | import manuelvicnt.com.rxjava_android_structure.networking.games.exception.GamesTechFailureException; 22 | import manuelvicnt.com.rxjava_android_structure.networking.login.LoginResponse; 23 | import manuelvicnt.com.rxjava_android_structure.networking.login.exception.LoginInternalException; 24 | import manuelvicnt.com.rxjava_android_structure.networking.login.exception.LoginTechFailureException; 25 | import manuelvicnt.com.rxjava_android_structure.networking.registration.exception.RegistrationNicknameAlreadyExistsException; 26 | import manuelvicnt.com.rxjava_android_structure.networking.registration.exception.RegistrationInternalException; 27 | import manuelvicnt.com.rxjava_android_structure.networking.registration.exception.RegistrationTechFailureException; 28 | import rx.Subscriber; 29 | import rx.Subscription; 30 | import rx.android.schedulers.AndroidSchedulers; 31 | 32 | /** 33 | * Created by ManuelVivo on 03/10/15. 34 | */ 35 | public class RegistrationFragment extends BaseFragment { 36 | 37 | private RegistrationViewModel registrationViewModel; 38 | private Subscription registrationSubscription; 39 | 40 | @Override 41 | public void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | 44 | AuthenticationRequestManager authenticationRequestManager = 45 | AuthenticationRequestManager.getInstance(getActivity().getApplicationContext()); 46 | registrationViewModel = new RegistrationViewModel(authenticationRequestManager); 47 | } 48 | 49 | @Override 50 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 51 | Bundle savedInstanceState) { 52 | 53 | View rootView = inflater.inflate(R.layout.fragment_registration, container, false); 54 | 55 | ButterKnife.bind(this, rootView); 56 | 57 | return rootView; 58 | } 59 | 60 | @Override 61 | protected void subscribeForNetworkRequests() { 62 | 63 | registrationSubscription = registrationViewModel.getRegistrationSubject() 64 | .observeOn(AndroidSchedulers.mainThread()) 65 | .subscribe(new RegistrationSubscriber()); 66 | } 67 | 68 | @Override 69 | protected void reconnectWithNetworkRequests() { 70 | 71 | registrationSubscription = registrationViewModel.createRegistrationSubject() 72 | .observeOn(AndroidSchedulers.mainThread()) 73 | .subscribe(new RegistrationSubscriber()); 74 | } 75 | 76 | @Override 77 | protected void unsubscribeFromNetworkRequests() { 78 | 79 | if (registrationSubscription != null) { 80 | registrationSubscription.unsubscribe(); 81 | } 82 | } 83 | 84 | @OnClick(R.id.register) 85 | public void registerButtonTap(View view) { 86 | 87 | AuthenticationManager.getInstance().setNickname("nickname"); 88 | AuthenticationManager.getInstance().setPassword("password"); 89 | registrationViewModel.register(); 90 | 91 | progressDialog = ProgressDialog.show(getActivity(), "Registering", "...", true); 92 | } 93 | 94 | private void showMessage(String message) { 95 | 96 | Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show(); 97 | } 98 | 99 | private void showMessageAndGoToLogin(String message) { 100 | 101 | showMessage(message); 102 | 103 | getActivity().setTitle("Login"); 104 | getActivity().getSupportFragmentManager().beginTransaction() 105 | .replace(R.id.fragment_container, new LoginFragment()).commit(); 106 | } 107 | 108 | private void launchHomeActivity() { 109 | 110 | Intent intent = new Intent(getActivity(), HomeActivity.class); 111 | startActivity(intent); 112 | getActivity().finish(); 113 | } 114 | 115 | private class RegistrationSubscriber extends Subscriber { 116 | 117 | @Override 118 | public void onCompleted() { 119 | 120 | hideProgressDialog(); 121 | launchHomeActivity(); 122 | } 123 | 124 | @Override 125 | public void onError(Throwable e) { 126 | 127 | hideProgressDialog(); 128 | 129 | // To be able to make another Registration request, 130 | // we have to reset the Subject in the VM 131 | reconnectWithNetworkRequests(); 132 | 133 | if (e instanceof RegistrationInternalException 134 | || e instanceof RegistrationTechFailureException) { 135 | 136 | showMessage("Registration Failure"); 137 | 138 | } else if (e instanceof RegistrationNicknameAlreadyExistsException) { 139 | 140 | showMessage("Registration Nickname already exists"); 141 | 142 | } else if (e instanceof LoginInternalException 143 | || e instanceof LoginTechFailureException) { 144 | 145 | showMessageAndGoToLogin("Login Failure"); 146 | 147 | } else if (e instanceof AccountTechFailureException) { 148 | 149 | showMessageAndGoToLogin("Account failed"); 150 | } else if (e instanceof GamesTechFailureException) { 151 | 152 | showMessageAndGoToLogin("Games failed"); 153 | } 154 | } 155 | 156 | @Override 157 | public void onNext(Object getUserDataResponse) { 158 | 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /app/src/main/java/manuelvicnt/com/rxjava_android_structure/registration/RegistrationViewModel.java: -------------------------------------------------------------------------------- 1 | package manuelvicnt.com.rxjava_android_structure.registration; 2 | 3 | import manuelvicnt.com.rxjava_android_structure.networking.AuthenticationRequestManager; 4 | import manuelvicnt.com.rxjava_android_structure.networking.login.LoginResponse; 5 | import rx.subjects.AsyncSubject; 6 | 7 | /** 8 | * Created by ManuelVivo on 03/10/15. 9 | */ 10 | public class RegistrationViewModel { 11 | 12 | private AuthenticationRequestManager authenticationRequestManager; 13 | private AsyncSubject registrationSubject; 14 | 15 | public RegistrationViewModel(AuthenticationRequestManager authenticationRequestManager) { 16 | 17 | this.authenticationRequestManager = authenticationRequestManager; 18 | registrationSubject = AsyncSubject.create(); 19 | } 20 | 21 | public AsyncSubject createRegistrationSubject() { 22 | 23 | registrationSubject = AsyncSubject.create(); 24 | return registrationSubject; 25 | } 26 | 27 | public AsyncSubject getRegistrationSubject() { 28 | 29 | return registrationSubject; 30 | } 31 | 32 | public void register() { 33 | 34 | authenticationRequestManager.register() 35 | .subscribe(registrationSubject); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_landing.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | 14 | 15 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_login.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 |