diff --git a/app/src/main/java/com/mirhoseini/marvel/AndroidModule.java b/app/src/main/java/com/mirhoseini/marvel/AndroidModule.java index 46e7deb..00e735e 100644 --- a/app/src/main/java/com/mirhoseini/marvel/AndroidModule.java +++ b/app/src/main/java/com/mirhoseini/marvel/AndroidModule.java @@ -1,9 +1,7 @@ package com.mirhoseini.marvel; import android.content.Context; -import android.content.SharedPreferences; import android.content.res.Resources; -import android.preference.PreferenceManager; import javax.inject.Singleton; diff --git a/app/src/main/java/com/mirhoseini/marvel/ApplicationComponent.java b/app/src/main/java/com/mirhoseini/marvel/ApplicationComponent.java index c933c06..253384d 100644 --- a/app/src/main/java/com/mirhoseini/marvel/ApplicationComponent.java +++ b/app/src/main/java/com/mirhoseini/marvel/ApplicationComponent.java @@ -3,9 +3,9 @@ import com.mirhoseini.marvel.activity.CharacterActivity; import com.mirhoseini.marvel.activity.MainActivity; import com.mirhoseini.marvel.activity.SplashActivity; -import com.mirhoseini.marvel.character.cache.AppCacheModule; +import com.mirhoseini.marvel.character.cache.CacheModule; import com.mirhoseini.marvel.character.cache.CacheSubComponent; -import com.mirhoseini.marvel.character.search.AppSearchModule; +import com.mirhoseini.marvel.character.search.SearchModule; import com.mirhoseini.marvel.character.search.SearchSubComponent; import com.mirhoseini.marvel.database.DatabaseModule; import com.mirhoseini.marvel.domain.ApiModule; @@ -35,8 +35,8 @@ public interface ApplicationComponent { void inject(CharacterActivity characterActivity); - SearchSubComponent plus(AppSearchModule module); + SearchSubComponent plus(SearchModule module); - CacheSubComponent plus(AppCacheModule module); + CacheSubComponent plus(CacheModule module); } \ No newline at end of file diff --git a/app/src/main/java/com/mirhoseini/marvel/ApplicationModule.java b/app/src/main/java/com/mirhoseini/marvel/ApplicationModule.java index c3f4a3b..2ffc9d6 100644 --- a/app/src/main/java/com/mirhoseini/marvel/ApplicationModule.java +++ b/app/src/main/java/com/mirhoseini/marvel/ApplicationModule.java @@ -2,9 +2,13 @@ import android.content.Context; +import com.google.firebase.analytics.FirebaseAnalytics; +import com.mirhoseini.marvel.util.AppConstants; import com.mirhoseini.marvel.util.AppSchedulerProvider; import com.mirhoseini.marvel.util.Constants; import com.mirhoseini.marvel.util.SchedulerProvider; +import com.mirhoseini.marvel.util.StateManager; +import com.mirhoseini.marvel.util.StateManagerImpl; import com.mirhoseini.utils.Utils; import java.io.File; @@ -33,7 +37,7 @@ boolean provideIsDebug() { @Singleton @Named("networkTimeoutInSeconds") int provideNetworkTimeoutInSeconds() { - return Constants.NETWORK_CONNECTION_TIMEOUT; + return AppConstants.NETWORK_CONNECTION_TIMEOUT; } @Provides @@ -52,21 +56,28 @@ SchedulerProvider provideAppScheduler() { @Singleton @Named("cacheSize") long provideCacheSize() { - return Constants.CACHE_SIZE; + return AppConstants.CACHE_SIZE; } @Provides @Singleton @Named("cacheMaxAge") int provideCacheMaxAgeMinutes() { - return Constants.CACHE_MAX_AGE; + return AppConstants.CACHE_MAX_AGE; } @Provides @Singleton @Named("cacheMaxStale") int provideCacheMaxStaleDays() { - return Constants.CACHE_MAX_STALE; + return AppConstants.CACHE_MAX_STALE; + } + + @Provides + @Singleton + @Named("retryCount") + public int provideApiRetryCount() { + return AppConstants.API_RETRY_COUNT; } @Provides @@ -82,4 +93,14 @@ boolean provideIsConnect(Context context) { return Utils.isConnected(context); } + @Provides + FirebaseAnalytics provideFirebaseAnalytics(Context context) { + return FirebaseAnalytics.getInstance(context); + } + + @Provides + @Singleton + public StateManager provideStateManager(StateManagerImpl stateManager) { + return stateManager; + } } diff --git a/app/src/main/java/com/mirhoseini/marvel/MarvelApplication.java b/app/src/main/java/com/mirhoseini/marvel/MarvelApplication.java index 8ca2328..17f6eec 100644 --- a/app/src/main/java/com/mirhoseini/marvel/MarvelApplication.java +++ b/app/src/main/java/com/mirhoseini/marvel/MarvelApplication.java @@ -1,6 +1,12 @@ package com.mirhoseini.marvel; import android.app.Application; +import android.content.Context; + +import com.mirhoseini.marvel.character.cache.CacheModule; +import com.mirhoseini.marvel.character.cache.CacheSubComponent; +import com.mirhoseini.marvel.character.search.SearchModule; +import com.mirhoseini.marvel.character.search.SearchSubComponent; /** * Created by Mohsen on 20/10/2016. @@ -9,11 +15,49 @@ public abstract class MarvelApplication extends Application { private static ApplicationComponent component; + private CacheSubComponent cacheSubComponent; + private SearchSubComponent searchSubComponent; public static ApplicationComponent getComponent() { return component; } + public static MarvelApplication get(Context context) { + return (MarvelApplication) context.getApplicationContext(); + } + + public CacheSubComponent getCacheSubComponent() { + if (null == cacheSubComponent) + createCacheSubComponent(); + + return cacheSubComponent; + } + + public CacheSubComponent createCacheSubComponent() { + cacheSubComponent = component.plus(new CacheModule()); + return cacheSubComponent; + } + + public void releaseCacheSubComponent() { + cacheSubComponent = null; + } + + public SearchSubComponent getSearchSubComponent() { + if (null == searchSubComponent) + createSearchSubComponent(); + + return searchSubComponent; + } + + public SearchSubComponent createSearchSubComponent() { + searchSubComponent = component.plus(new SearchModule()); + return searchSubComponent; + } + + public void releaseSearchSubComponent() { + searchSubComponent = null; + } + @Override public void onCreate() { super.onCreate(); diff --git a/app/src/main/java/com/mirhoseini/marvel/activity/CharacterActivity.java b/app/src/main/java/com/mirhoseini/marvel/activity/CharacterActivity.java index 1e1110a..cb03598 100644 --- a/app/src/main/java/com/mirhoseini/marvel/activity/CharacterActivity.java +++ b/app/src/main/java/com/mirhoseini/marvel/activity/CharacterActivity.java @@ -9,6 +9,7 @@ import com.mirhoseini.marvel.ApplicationComponent; import com.mirhoseini.marvel.BR; +import com.mirhoseini.marvel.MarvelApplication; import com.mirhoseini.marvel.R; import com.mirhoseini.marvel.base.BaseActivity; import com.mirhoseini.marvel.database.model.CharacterModel; @@ -55,6 +56,11 @@ protected void onCreate(Bundle savedInstanceState) { Timber.d("Character Activity Created"); } + @Override + protected void injectDependencies(MarvelApplication application, ApplicationComponent component) { + component.inject(this); + } + private void setupToolbar(String characterName) { setSupportActionBar(toolbar); toolbar.setNavigationIcon(R.drawable.logo); @@ -63,8 +69,8 @@ private void setupToolbar(String characterName) { } @Override - protected void injectDependencies(ApplicationComponent component) { - component.inject(this); + protected void releaseSubComponents(MarvelApplication application) { + } } diff --git a/app/src/main/java/com/mirhoseini/marvel/activity/MainActivity.java b/app/src/main/java/com/mirhoseini/marvel/activity/MainActivity.java index a08cdaf..73122d2 100644 --- a/app/src/main/java/com/mirhoseini/marvel/activity/MainActivity.java +++ b/app/src/main/java/com/mirhoseini/marvel/activity/MainActivity.java @@ -2,7 +2,6 @@ import android.content.Context; import android.content.Intent; -import android.content.res.Resources; import android.graphics.Color; import android.os.Bundle; import android.provider.Settings; @@ -12,23 +11,25 @@ import android.widget.Toast; import com.mirhoseini.marvel.ApplicationComponent; +import com.mirhoseini.marvel.MarvelApplication; import com.mirhoseini.marvel.R; import com.mirhoseini.marvel.base.BaseActivity; -import com.mirhoseini.marvel.character.cache.CharacterCacheFragment; -import com.mirhoseini.marvel.character.search.CharacterSearchFragment; +import com.mirhoseini.marvel.character.cache.CacheFragment; +import com.mirhoseini.marvel.character.search.SearchFragment; import com.mirhoseini.marvel.database.model.CharacterModel; import javax.inject.Inject; import butterknife.BindView; import butterknife.ButterKnife; +import rx.subscriptions.CompositeSubscription; import timber.log.Timber; /** * Created by Mohsen on 20/10/2016. */ -public class MainActivity extends BaseActivity implements CharacterSearchFragment.OnListFragmentInteractionListener, CharacterCacheFragment.OnListFragmentInteractionListener { +public class MainActivity extends BaseActivity { public static final String TAG_SEARCH_FRAGMENT = "search_fragment"; public static final String TAG_CACHE_FRAGMENT = "cache_fragment"; @@ -36,19 +37,19 @@ public class MainActivity extends BaseActivity implements CharacterSearchFragmen // injecting dependencies via Dagger @Inject Context context; - @Inject - Resources resources; // injecting views via ButterKnife @BindView(R.id.toolbar) Toolbar toolbar; - private CharacterSearchFragment searchFragment; - private CharacterCacheFragment cacheFragment; + CompositeSubscription subscriptions; + private SearchFragment searchFragment; + private CacheFragment cacheFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); // inject views using ButterKnife @@ -57,19 +58,19 @@ protected void onCreate(Bundle savedInstanceState) { setupToolbar(); if (null == savedInstanceState) { - searchFragment = CharacterSearchFragment.newInstance(); - cacheFragment = CharacterCacheFragment.newInstance(); + searchFragment = SearchFragment.newInstance(); + cacheFragment = CacheFragment.newInstance(); attachFragments(); } else { - searchFragment = (CharacterSearchFragment) getSupportFragmentManager().findFragmentByTag(TAG_SEARCH_FRAGMENT); - cacheFragment = (CharacterCacheFragment) getSupportFragmentManager().findFragmentByTag(TAG_CACHE_FRAGMENT); + searchFragment = (SearchFragment) getSupportFragmentManager().findFragmentByTag(TAG_SEARCH_FRAGMENT); + cacheFragment = (CacheFragment) getSupportFragmentManager().findFragmentByTag(TAG_CACHE_FRAGMENT); } Timber.d("Main Activity Created"); } @Override - protected void injectDependencies(ApplicationComponent component) { + protected void injectDependencies(MarvelApplication application, ApplicationComponent component) { component.inject(this); } @@ -87,27 +88,59 @@ private void attachFragments() { } @Override + protected void onResume() { + super.onResume(); + + if (null == subscriptions || subscriptions.isUnsubscribed()) + subscriptions = new CompositeSubscription(); + + subscriptions.addAll( + searchFragment.characterObservable() + .subscribe(this::showCharacter), + searchFragment.messageObservable() + .subscribe(this::showMessage), + searchFragment.offlineObservable() + .subscribe(this::showOfflineMessage), + cacheFragment.characterObservable() + .subscribe(this::showCharacter), + cacheFragment.messageObservable() + .subscribe(this::showMessage), + cacheFragment.offlineObservable() + .subscribe(this::showOfflineMessage) + ); + } + public void showMessage(String message) { Timber.d("Showing Message: %s", message); Toast.makeText(context, message, Toast.LENGTH_LONG).show(); } - @Override - public void showOfflineMessage() { + public void showOfflineMessage(boolean isCritical) { Timber.d("Showing Offline Message"); Snackbar.make(toolbar, R.string.offline_message, Snackbar.LENGTH_LONG) - .setAction(R.string.go_online, v -> { - startActivity(new Intent( - Settings.ACTION_WIFI_SETTINGS)); - }) + .setAction(R.string.go_online, v -> startActivity(new Intent( + Settings.ACTION_WIFI_SETTINGS))) .setActionTextColor(Color.GREEN) .show(); } - @Override public void showCharacter(CharacterModel character) { startActivity(CharacterActivity.newIntent(this, character)); } + + @Override + protected void onPause() { + super.onPause(); + + subscriptions.unsubscribe(); + } + + @Override + protected void releaseSubComponents(MarvelApplication application) { + application.releaseCacheSubComponent(); + application.releaseSearchSubComponent(); + } + } diff --git a/app/src/main/java/com/mirhoseini/marvel/activity/SplashActivity.java b/app/src/main/java/com/mirhoseini/marvel/activity/SplashActivity.java index 4790c59..c92a7b9 100644 --- a/app/src/main/java/com/mirhoseini/marvel/activity/SplashActivity.java +++ b/app/src/main/java/com/mirhoseini/marvel/activity/SplashActivity.java @@ -6,6 +6,7 @@ import android.view.MotionEvent; import com.mirhoseini.marvel.ApplicationComponent; +import com.mirhoseini.marvel.MarvelApplication; import com.mirhoseini.marvel.R; import com.mirhoseini.marvel.base.BaseActivity; import com.mirhoseini.marvel.util.AppConstants; @@ -61,6 +62,11 @@ public void run() { splashThread.start(); } + @Override + protected void injectDependencies(MarvelApplication application, ApplicationComponent component) { + component.inject(this); + } + // Listening to whole activity touch events @Override public boolean onTouchEvent(MotionEvent evt) { @@ -74,7 +80,8 @@ public boolean onTouchEvent(MotionEvent evt) { } @Override - protected void injectDependencies(ApplicationComponent component) { - component.inject(this); + protected void releaseSubComponents(MarvelApplication application) { + } + } \ No newline at end of file diff --git a/app/src/main/java/com/mirhoseini/marvel/base/BaseActivity.java b/app/src/main/java/com/mirhoseini/marvel/base/BaseActivity.java index 8643aaf..9bba442 100644 --- a/app/src/main/java/com/mirhoseini/marvel/base/BaseActivity.java +++ b/app/src/main/java/com/mirhoseini/marvel/base/BaseActivity.java @@ -16,11 +16,20 @@ public abstract class BaseActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - injectDependencies(MarvelApplication.getComponent()); + injectDependencies(MarvelApplication.get(this), MarvelApplication.getComponent()); // can be used for general purpose in all Activities of Application } - protected abstract void injectDependencies(ApplicationComponent component); + protected abstract void injectDependencies(MarvelApplication application, ApplicationComponent component); + + @Override + public void finish() { + super.finish(); + + releaseSubComponents(MarvelApplication.get(this)); + } + + protected abstract void releaseSubComponents(MarvelApplication application); } diff --git a/app/src/main/java/com/mirhoseini/marvel/base/BaseFragment.java b/app/src/main/java/com/mirhoseini/marvel/base/BaseFragment.java index 88cb600..a5a7bd8 100644 --- a/app/src/main/java/com/mirhoseini/marvel/base/BaseFragment.java +++ b/app/src/main/java/com/mirhoseini/marvel/base/BaseFragment.java @@ -3,7 +3,6 @@ import android.content.Context; import android.support.v4.app.Fragment; -import com.mirhoseini.marvel.ApplicationComponent; import com.mirhoseini.marvel.MarvelApplication; /** @@ -16,11 +15,11 @@ public abstract class BaseFragment extends Fragment { public void onAttach(Context context) { super.onAttach(context); - injectDependencies(MarvelApplication.getComponent(), context); + injectDependencies(MarvelApplication.get(getContext())); // can be used for general purpose in all Fragments of Application } - protected abstract void injectDependencies(ApplicationComponent component, Context context); + protected abstract void injectDependencies(MarvelApplication application); } \ No newline at end of file diff --git a/app/src/main/java/com/mirhoseini/marvel/character/cache/AppCacheModule.java b/app/src/main/java/com/mirhoseini/marvel/character/cache/AppCacheModule.java deleted file mode 100644 index 3ddde30..0000000 --- a/app/src/main/java/com/mirhoseini/marvel/character/cache/AppCacheModule.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.mirhoseini.marvel.character.cache; - -import android.content.Context; -import android.support.v7.widget.GridLayoutManager; - -import com.mirhoseini.marvel.util.AppConstants; -import com.mirhoseini.marvel.util.GridSpacingItemDecoration; - -import dagger.Module; -import dagger.Provides; - -/** - * Created by Mohsen on 20/10/2016. - */ - -@Module -public class AppCacheModule extends CacheModule { - private final Context context; - private final int columnCount; - private final CharacterCacheFragment.OnListFragmentInteractionListener listener; - - AppCacheModule(Context context, CharacterCacheFragment fragment, int columnCount) { - super(fragment); - - this.context = context; - this.columnCount = columnCount; - - if (context instanceof CharacterCacheFragment.OnListFragmentInteractionListener) { - listener = (CharacterCacheFragment.OnListFragmentInteractionListener) context; - } else { - throw new RuntimeException(context.toString() - + " must implement OnListFragmentInteractionListener"); - } - } - - @Provides - @Cache - CharacterCacheFragment.OnListFragmentInteractionListener provideOnListFragmentInteractionListener() { - return listener; - } - - @Provides - @Cache - public GridLayoutManager provideLayoutManager() { - GridLayoutManager gridLayoutManager = new GridLayoutManager(context, columnCount); - // Create a custom SpanSizeLookup where the first item spans both columns - gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { - @Override - public int getSpanSize(int position) { - return position == 0 ? columnCount : 1; - } - }); - - return gridLayoutManager; - } - - @Provides - @Cache - public GridSpacingItemDecoration provideGridSpacingItemDecoration() { - return new GridSpacingItemDecoration(columnCount, AppConstants.RECYCLER_VIEW_ITEM_SPACE, true, 1); - } -} diff --git a/app/src/main/java/com/mirhoseini/marvel/character/cache/CharacterCacheFragment.java b/app/src/main/java/com/mirhoseini/marvel/character/cache/CacheFragment.java similarity index 58% rename from app/src/main/java/com/mirhoseini/marvel/character/cache/CharacterCacheFragment.java rename to app/src/main/java/com/mirhoseini/marvel/character/cache/CacheFragment.java index 9728100..7243360 100644 --- a/app/src/main/java/com/mirhoseini/marvel/character/cache/CharacterCacheFragment.java +++ b/app/src/main/java/com/mirhoseini/marvel/character/cache/CacheFragment.java @@ -9,12 +9,12 @@ import android.view.ViewGroup; import android.widget.Toast; -import com.mirhoseini.marvel.ApplicationComponent; -import com.mirhoseini.marvel.base.BaseView; +import com.mirhoseini.marvel.MarvelApplication; import com.mirhoseini.marvel.R; import com.mirhoseini.marvel.base.BaseFragment; import com.mirhoseini.marvel.character.cache.adapter.CharactersRecyclerViewAdapter; import com.mirhoseini.marvel.database.model.CharacterModel; +import com.mirhoseini.marvel.util.AppConstants; import com.mirhoseini.marvel.util.GridSpacingItemDecoration; import java.util.List; @@ -23,6 +23,8 @@ import butterknife.BindView; import butterknife.ButterKnife; +import rx.Observable; +import rx.subjects.PublishSubject; import rx.subscriptions.CompositeSubscription; import timber.log.Timber; @@ -30,21 +32,16 @@ * Created by Mohsen on 20/10/2016. */ -public class CharacterCacheFragment extends BaseFragment implements CacheView { +public class CacheFragment extends BaseFragment implements CacheView { + public static final int COLUMN_COUNT = 2; // injecting dependencies via Dagger @Inject - CachePresenter presenter; - @Inject Context context; @Inject - GridLayoutManager layoutManager; - @Inject - GridSpacingItemDecoration gridSpacingItemDecoration; + CachePresenter presenter; @Inject CharactersRecyclerViewAdapter adapter; - @Inject - OnListFragmentInteractionListener listener; // injecting views via ButterKnife @BindView(R.id.list) @@ -52,17 +49,23 @@ public class CharacterCacheFragment extends BaseFragment implements CacheView { @BindView(R.id.empty) ViewGroup empty; - private CompositeSubscription subscriptions = new CompositeSubscription(); + CompositeSubscription subscriptions = new CompositeSubscription(); + GridLayoutManager gridLayoutManager; + GridSpacingItemDecoration gridSpacingItemDecoration; + + PublishSubject notifyCharacter = PublishSubject.create(); + PublishSubject notifyMessage = PublishSubject.create(); + PublishSubject notifyOffline = PublishSubject.create(); /** * Mandatory empty constructor for the fragment manager to instantiate the * fragment (e.g. upon screen orientation changes). */ - public CharacterCacheFragment() { + public CacheFragment() { } - public static CharacterCacheFragment newInstance() { - CharacterCacheFragment fragment = new CharacterCacheFragment(); + public static CacheFragment newInstance() { + CacheFragment fragment = new CacheFragment(); return fragment; } @@ -70,10 +73,18 @@ public static CharacterCacheFragment newInstance() { public void onAttach(Context context) { super.onAttach(context); + presenter.bind(this); + subscriptions.add( adapter.asObservable() - .filter(characterModel -> null != listener) - .subscribe(listener::showCharacter)); + .subscribe(notifyCharacter::onNext)); + } + + @Override + protected void injectDependencies(MarvelApplication application) { + application + .getCacheSubComponent() + .inject(this); } @Override @@ -95,43 +106,48 @@ public void onResume() { presenter.loadLast5CharactersCachedData(); } - @Override - protected void injectDependencies(ApplicationComponent component, Context context) { - component - .plus(new AppCacheModule(context, this, 2)) - .inject(this); - } - @Override public void onDetach() { super.onDetach(); - listener = null; presenter.unbind(); - presenter = null; subscriptions.unsubscribe(); } @Override public void showMessage(String message) { - if (null != listener) { - listener.showMessage(message); - } + notifyMessage.onNext(message); } @Override - public void showOfflineMessage() { - if (null != listener) { - listener.showOfflineMessage(); - } + public void showOfflineMessage(boolean isCritical) { + notifyOffline.onNext(isCritical); } private void initRecyclerView() { - list.setLayoutManager(layoutManager); + initLayoutManager(); + initGridSpacingItemDecoration(); + + list.setLayoutManager(gridLayoutManager); list.addItemDecoration(gridSpacingItemDecoration); } + public void initLayoutManager() { + gridLayoutManager = new GridLayoutManager(context, COLUMN_COUNT); + // Create a custom SpanSizeLookup where the first item spans both columns + gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + return position == 0 ? COLUMN_COUNT : 1; + } + }); + } + + public void initGridSpacingItemDecoration() { + gridSpacingItemDecoration = new GridSpacingItemDecoration(COLUMN_COUNT, AppConstants.RECYCLER_VIEW_ITEM_SPACE, true, 1); + } + @Override public void setLast5CharactersCachedData(List characterModels) { if (characterModels.size() > 0) { @@ -153,9 +169,16 @@ public void showError(Throwable throwable) { Toast.makeText(context, throwable.getMessage(), Toast.LENGTH_LONG).show(); } - public interface OnListFragmentInteractionListener extends BaseView { + public Observable characterObservable() { + return notifyCharacter.asObservable(); + } - void showCharacter(CharacterModel character); + public Observable messageObservable() { + return notifyMessage.asObservable(); + } + public Observable offlineObservable() { + return notifyOffline.asObservable(); } + } diff --git a/app/src/main/java/com/mirhoseini/marvel/character/cache/CacheSubComponent.java b/app/src/main/java/com/mirhoseini/marvel/character/cache/CacheSubComponent.java index 3827a4b..f38a5f9 100644 --- a/app/src/main/java/com/mirhoseini/marvel/character/cache/CacheSubComponent.java +++ b/app/src/main/java/com/mirhoseini/marvel/character/cache/CacheSubComponent.java @@ -8,10 +8,10 @@ @Cache @Subcomponent(modules = { - AppCacheModule.class + CacheModule.class }) public interface CacheSubComponent { - void inject(CharacterCacheFragment fragment); + void inject(CacheFragment fragment); } diff --git a/app/src/main/java/com/mirhoseini/marvel/character/search/AppSearchModule.java b/app/src/main/java/com/mirhoseini/marvel/character/search/AppSearchModule.java deleted file mode 100644 index 799c394..0000000 --- a/app/src/main/java/com/mirhoseini/marvel/character/search/AppSearchModule.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.mirhoseini.marvel.character.search; - -import android.content.Context; - -import dagger.Module; -import dagger.Provides; - -/** - * Created by Mohsen on 20/10/2016. - */ - -@Module -public class AppSearchModule extends SearchModule { - private final CharacterSearchFragment.OnListFragmentInteractionListener listener; - - AppSearchModule(Context context, CharacterSearchFragment fragment) { - super(fragment); - - if (context instanceof CharacterSearchFragment.OnListFragmentInteractionListener) { - listener = (CharacterSearchFragment.OnListFragmentInteractionListener) context; - } else { - throw new RuntimeException(context.toString() - + " must implement OnListFragmentInteractionListener"); - } - } - - @Provides - @Search - CharacterSearchFragment.OnListFragmentInteractionListener provideOnListFragmentInteractionListener() { - return listener; - } - -} diff --git a/app/src/main/java/com/mirhoseini/marvel/character/search/CharacterSearchFragment.java b/app/src/main/java/com/mirhoseini/marvel/character/search/SearchFragment.java similarity index 81% rename from app/src/main/java/com/mirhoseini/marvel/character/search/CharacterSearchFragment.java rename to app/src/main/java/com/mirhoseini/marvel/character/search/SearchFragment.java index b4c3ec5..6362f83 100644 --- a/app/src/main/java/com/mirhoseini/marvel/character/search/CharacterSearchFragment.java +++ b/app/src/main/java/com/mirhoseini/marvel/character/search/SearchFragment.java @@ -16,8 +16,7 @@ import android.widget.EditText; import com.google.firebase.analytics.FirebaseAnalytics; -import com.mirhoseini.marvel.ApplicationComponent; -import com.mirhoseini.marvel.base.BaseView; +import com.mirhoseini.marvel.MarvelApplication; import com.mirhoseini.marvel.R; import com.mirhoseini.marvel.base.BaseFragment; import com.mirhoseini.marvel.database.model.CharacterModel; @@ -31,13 +30,15 @@ import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.OnEditorAction; +import rx.Observable; +import rx.subjects.PublishSubject; import timber.log.Timber; /** * Created by Mohsen on 20/10/2016. */ -public class CharacterSearchFragment extends BaseFragment implements SearchView { +public class SearchFragment extends BaseFragment implements SearchView { // injecting dependencies via Dagger @Inject @@ -45,9 +46,9 @@ public class CharacterSearchFragment extends BaseFragment implements SearchView @Inject Resources resources; @Inject - SearchPresenter presenter; + FirebaseAnalytics firebaseAnalytics; @Inject - OnListFragmentInteractionListener listener; + SearchPresenter presenter; // injecting views via ButterKnife @BindView(R.id.character) @@ -55,18 +56,21 @@ public class CharacterSearchFragment extends BaseFragment implements SearchView @BindView(R.id.show) Button show; - private FirebaseAnalytics firebaseAnalytics; - private ProgressDialog progressDialog; + ProgressDialog progressDialog; + + PublishSubject notifyCharacter = PublishSubject.create(); + PublishSubject notifyMessage = PublishSubject.create(); + PublishSubject notifyOffline = PublishSubject.create(); /** * Mandatory empty constructor for the fragment manager to instantiate the * fragment (e.g. upon screen orientation changes). */ - public CharacterSearchFragment() { + public SearchFragment() { } - public static CharacterSearchFragment newInstance() { - CharacterSearchFragment fragment = new CharacterSearchFragment(); + public static SearchFragment newInstance() { + SearchFragment fragment = new SearchFragment(); return fragment; } @@ -102,6 +106,20 @@ private void logFirebaseAnalyticsSearchEvent(String query) { firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SEARCH, bundle); } + @Override + public void onAttach(Context context) { + super.onAttach(context); + + presenter.bind(this); + } + + @Override + protected void injectDependencies(MarvelApplication application) { + application + .getSearchSubComponent() + .inject(this); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -112,14 +130,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, return view; } - @Override - public void onAttach(Context context) { - super.onAttach(context); - - // Obtain the FirebaseAnalytics instance. - firebaseAnalytics = FirebaseAnalytics.getInstance(context); - } - @Override public void onResume() { super.onResume(); @@ -132,34 +142,21 @@ public void setCharactersCachedData(List characters) { character.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, characters)); } - @Override - protected void injectDependencies(ApplicationComponent component, Context context) { - component - .plus(new AppSearchModule(context, this)) - .inject(this); - } - @Override public void onDetach() { super.onDetach(); - listener = null; presenter.unbind(); - presenter = null; } @Override public void showMessage(String message) { - if (null != listener) { - listener.showMessage(message); - } + notifyMessage.onNext(message); } @Override - public void showOfflineMessage() { - if (null != listener) { - listener.showOfflineMessage(); - } + public void showOfflineMessage(boolean isCritical) { + notifyOffline.onNext(isCritical); } @Override @@ -219,8 +216,7 @@ public void showQueryError(Throwable throwable) { public void showCharacter(CharacterModel character) { logFirebaseAnalyticsSelectEvent(character); - if (null != listener) - listener.showCharacter(character); + notifyCharacter.onNext(character); } private void logFirebaseAnalyticsSelectEvent(CharacterModel character) { @@ -230,10 +226,16 @@ private void logFirebaseAnalyticsSelectEvent(CharacterModel character) { firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle); } - public interface OnListFragmentInteractionListener extends BaseView { + public Observable characterObservable() { + return notifyCharacter.asObservable(); + } - void showCharacter(CharacterModel character); + public Observable messageObservable() { + return notifyMessage.asObservable(); + } + public Observable offlineObservable() { + return notifyOffline.asObservable(); } } diff --git a/app/src/main/java/com/mirhoseini/marvel/character/search/SearchSubComponent.java b/app/src/main/java/com/mirhoseini/marvel/character/search/SearchSubComponent.java index e37fb6a..10e3ce4 100644 --- a/app/src/main/java/com/mirhoseini/marvel/character/search/SearchSubComponent.java +++ b/app/src/main/java/com/mirhoseini/marvel/character/search/SearchSubComponent.java @@ -8,10 +8,10 @@ @Search @Subcomponent(modules = { - AppSearchModule.class + SearchModule.class }) public interface SearchSubComponent { - void inject(CharacterSearchFragment fragment); + void inject(SearchFragment fragment); } diff --git a/app/src/main/java/com/mirhoseini/marvel/util/AppConstants.java b/app/src/main/java/com/mirhoseini/marvel/util/AppConstants.java index 3489b7f..7eb8685 100644 --- a/app/src/main/java/com/mirhoseini/marvel/util/AppConstants.java +++ b/app/src/main/java/com/mirhoseini/marvel/util/AppConstants.java @@ -10,4 +10,5 @@ public class AppConstants extends Constants { public static final int RECYCLER_VIEW_ITEM_SPACE = 48; + public static final int API_RETRY_COUNT = 3; } diff --git a/app/src/main/java/com/mirhoseini/marvel/util/StateManagerImpl.java b/app/src/main/java/com/mirhoseini/marvel/util/StateManagerImpl.java new file mode 100644 index 0000000..58629c1 --- /dev/null +++ b/app/src/main/java/com/mirhoseini/marvel/util/StateManagerImpl.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016 Karina Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ + +package com.mirhoseini.marvel.util; + +import android.content.Context; + +import com.mirhoseini.utils.Utils; + +import javax.inject.Inject; + +/** + * Created by Mohsen on 06/11/2016. + */ + +public class StateManagerImpl implements StateManager { + + private Context context; + + @Inject + public StateManagerImpl(Context context) { + this.context = context; + } + + @Override + public boolean isConnect() { + return Utils.isConnected(context); + } +} diff --git a/app/src/test/java/com/mirhoseini/marvel/activity/CharacterActivityTest.java b/app/src/test/java/com/mirhoseini/marvel/activity/CharacterActivityTest.java index 01fb2d9..4eb9aaa 100644 --- a/app/src/test/java/com/mirhoseini/marvel/activity/CharacterActivityTest.java +++ b/app/src/test/java/com/mirhoseini/marvel/activity/CharacterActivityTest.java @@ -28,12 +28,12 @@ @Config(constants = BuildConfig.class, sdk = 21, shadows = {ShadowSnackbar.class}) public class CharacterActivityTest { - public static final String TEST_CHARACTER_NAME = "Test Name"; - public static final String TEST_CHARACTER_DESCRIPTION = "Test Description"; - public static final String TEST_CHARACTER_THUMBNAIL = "Test Thumbnail"; + static final String TEST_CHARACTER_NAME = "Test Name"; + static final String TEST_CHARACTER_DESCRIPTION = "Test Description"; + static final String TEST_CHARACTER_THUMBNAIL = "Test Thumbnail"; - private CharacterActivity activity; - private CharacterModel character; + CharacterActivity activity; + CharacterModel character; @Before public void setUp() throws Exception { diff --git a/app/src/test/java/com/mirhoseini/marvel/activity/MainActivityRobolectricTest.java b/app/src/test/java/com/mirhoseini/marvel/activity/MainActivityRobolectricTest.java index ea1e714..f6f03d3 100644 --- a/app/src/test/java/com/mirhoseini/marvel/activity/MainActivityRobolectricTest.java +++ b/app/src/test/java/com/mirhoseini/marvel/activity/MainActivityRobolectricTest.java @@ -43,12 +43,4 @@ public void testShowToastMessage() throws Exception { assertThat(TEST_TEXT, equalTo(ShadowToast.getTextOfLatestToast())); } - @Test - public void testShowOfflineMessage() throws Exception { - activity.showOfflineMessage(); - - assertSnackbarIsShown(R.string.offline_message); - } - - } diff --git a/app/src/test/java/com/mirhoseini/marvel/character/search/CharacterSearchFragmentRobolectricTest.java b/app/src/test/java/com/mirhoseini/marvel/character/search/SearchFragmentRobolectricTest.java similarity index 65% rename from app/src/test/java/com/mirhoseini/marvel/character/search/CharacterSearchFragmentRobolectricTest.java rename to app/src/test/java/com/mirhoseini/marvel/character/search/SearchFragmentRobolectricTest.java index 901bb86..741b27a 100644 --- a/app/src/test/java/com/mirhoseini/marvel/character/search/CharacterSearchFragmentRobolectricTest.java +++ b/app/src/test/java/com/mirhoseini/marvel/character/search/SearchFragmentRobolectricTest.java @@ -26,10 +26,10 @@ @RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21, shadows = {ShadowSnackbar.class}) -public class CharacterSearchFragmentRobolectricTest { +public class SearchFragmentRobolectricTest { private MainActivity activity; - private CharacterSearchFragment fragment; + private SearchFragment fragment; @Before public void setUp() throws Exception { @@ -38,7 +38,7 @@ public void setUp() throws Exception { assertNotNull(activity); // setup fragment - fragment = (CharacterSearchFragment) activity.getSupportFragmentManager().findFragmentByTag(MainActivity.TAG_SEARCH_FRAGMENT); + fragment = (SearchFragment) activity.getSupportFragmentManager().findFragmentByTag(MainActivity.TAG_SEARCH_FRAGMENT); assertNotNull(fragment); } @@ -58,40 +58,11 @@ public void testHideProgress() throws Exception { assertProgressDialogIsShown(R.string.please_wait); } - @Test - public void testShowError() throws Exception { - fragment.showError(new Throwable("unknown error")); - - assertSnackbarIsShown(R.string.retry_message); - } - - @Test - public void testShowRetryMessage() throws Exception { - fragment.showRetryMessage(new Throwable("unknown error")); - - assertSnackbarIsShown(R.string.retry_message); - } - - @Test - public void testShowQueryError() throws Exception { - fragment.showQueryError(new Throwable("unknown error")); - - assertSnackbarIsShown(R.string.retry_message); - } - @Test public void testShowQueryNoResult() throws Exception { fragment.showQueryNoResult(); assertThat(activity.getString(R.string.no_result), equalTo(ShadowToast.getTextOfLatestToast())); } - - @Test - public void testOnDestroy() throws Exception { - fragment.onDetach(); - - assertNull(fragment.listener); - assertNull(fragment.presenter); - } - + } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 0887a9c..bbc8e9e 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.2' + classpath 'com.android.tools.build:gradle:2.2.3' classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1' classpath 'com.google.gms:google-services:3.0.0' classpath 'me.tatarka:gradle-retrolambda:3.3.1' diff --git a/console/src/main/java/com/mirhoseini/marvel/ConsoleComponent.java b/console/src/main/java/com/mirhoseini/marvel/ConsoleComponent.java index 143ec25..01856d5 100644 --- a/console/src/main/java/com/mirhoseini/marvel/ConsoleComponent.java +++ b/console/src/main/java/com/mirhoseini/marvel/ConsoleComponent.java @@ -1,6 +1,6 @@ package com.mirhoseini.marvel; -import com.mirhoseini.marvel.character.search.ConsoleSearchModule; +import com.mirhoseini.marvel.character.search.SearchModule; import com.mirhoseini.marvel.character.search.SearchSubComponent; import com.mirhoseini.marvel.database.DatabaseModule; import com.mirhoseini.marvel.domain.ApiModule; @@ -25,6 +25,6 @@ public interface ConsoleComponent { void inject(MainClass main); - SearchSubComponent plus(ConsoleSearchModule module); + SearchSubComponent plus(SearchModule module); } \ No newline at end of file diff --git a/console/src/main/java/com/mirhoseini/marvel/ConsoleModule.java b/console/src/main/java/com/mirhoseini/marvel/ConsoleModule.java index d928823..802a0cb 100644 --- a/console/src/main/java/com/mirhoseini/marvel/ConsoleModule.java +++ b/console/src/main/java/com/mirhoseini/marvel/ConsoleModule.java @@ -1,8 +1,11 @@ package com.mirhoseini.marvel; +import com.mirhoseini.marvel.util.ConsoleConstants; import com.mirhoseini.marvel.util.ConsoleSchedulerProvider; import com.mirhoseini.marvel.util.Constants; import com.mirhoseini.marvel.util.SchedulerProvider; +import com.mirhoseini.marvel.util.StateManager; +import com.mirhoseini.marvel.util.StateManagerImpl; import java.io.File; @@ -31,13 +34,13 @@ boolean provideIsDebug() { @Singleton @Named("networkTimeoutInSeconds") int provideNetworkTimeoutInSeconds() { - return Constants.NETWORK_CONNECTION_TIMEOUT; + return ConsoleConstants.NETWORK_CONNECTION_TIMEOUT; } @Provides @Singleton HttpUrl provideEndpoint() { - return HttpUrl.parse(Constants.BASE_URL); + return HttpUrl.parse(ConsoleConstants.BASE_URL); } @Provides @@ -50,21 +53,28 @@ SchedulerProvider provideAppScheduler() { @Singleton @Named("cacheSize") long provideCacheSize() { - return Constants.CACHE_SIZE; + return ConsoleConstants.CACHE_SIZE; } @Provides @Singleton @Named("cacheMaxAge") int provideCacheMaxAgeMinutes() { - return Constants.CACHE_MAX_AGE; + return ConsoleConstants.CACHE_MAX_AGE; } @Provides @Singleton @Named("cacheMaxStale") int provideCacheMaxStaleDays() { - return Constants.CACHE_MAX_STALE; + return ConsoleConstants.CACHE_MAX_STALE; + } + + @Provides + @Singleton + @Named("retryCount") + public int provideApiRetryCount() { + return ConsoleConstants.API_RETRY_COUNT; } @Provides @@ -80,4 +90,9 @@ boolean provideIsConnect() { return true; } + @Provides + @Singleton + public StateManager provideStateManager(StateManagerImpl stateManager) { + return stateManager; + } } diff --git a/console/src/main/java/com/mirhoseini/marvel/character/search/CharacterSearch.java b/console/src/main/java/com/mirhoseini/marvel/character/search/CharacterSearch.java index 6fe35cb..db1d4ae 100644 --- a/console/src/main/java/com/mirhoseini/marvel/character/search/CharacterSearch.java +++ b/console/src/main/java/com/mirhoseini/marvel/character/search/CharacterSearch.java @@ -16,8 +16,10 @@ public class CharacterSearch implements SearchView { SearchPresenter presenter; public CharacterSearch(ConsoleComponent component) { - component.plus(new ConsoleSearchModule(this)) + component.plus(new SearchModule()) .inject(this); + + presenter.bind(this); } public void doSearch(String query) { @@ -75,7 +77,7 @@ public void showMessage(String message) { } @Override - public void showOfflineMessage() { + public void showOfflineMessage(boolean isCritical) { System.out.println("Offline!!!"); } } diff --git a/console/src/main/java/com/mirhoseini/marvel/character/search/ConsoleSearchModule.java b/console/src/main/java/com/mirhoseini/marvel/character/search/ConsoleSearchModule.java deleted file mode 100644 index 113b681..0000000 --- a/console/src/main/java/com/mirhoseini/marvel/character/search/ConsoleSearchModule.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.mirhoseini.marvel.character.search; - -import dagger.Module; - -/** - * Created by Mohsen on 20/10/2016. - */ - -@Module -public class ConsoleSearchModule extends SearchModule { - - ConsoleSearchModule(SearchView view) { - super(view); - } -} diff --git a/console/src/main/java/com/mirhoseini/marvel/character/search/SearchSubComponent.java b/console/src/main/java/com/mirhoseini/marvel/character/search/SearchSubComponent.java index 77533f8..49d52c6 100644 --- a/console/src/main/java/com/mirhoseini/marvel/character/search/SearchSubComponent.java +++ b/console/src/main/java/com/mirhoseini/marvel/character/search/SearchSubComponent.java @@ -8,7 +8,7 @@ @Search @Subcomponent(modules = { - ConsoleSearchModule.class + SearchModule.class }) public interface SearchSubComponent { diff --git a/console/src/main/java/com/mirhoseini/marvel/util/ConsoleConstants.java b/console/src/main/java/com/mirhoseini/marvel/util/ConsoleConstants.java new file mode 100644 index 0000000..4b7a460 --- /dev/null +++ b/console/src/main/java/com/mirhoseini/marvel/util/ConsoleConstants.java @@ -0,0 +1,10 @@ +package com.mirhoseini.marvel.util; + +/** + * Created by Mohsen on 10/01/2017. + */ +public class ConsoleConstants extends Constants { + + public static final int API_RETRY_COUNT = 3; + +} diff --git a/console/src/main/java/com/mirhoseini/marvel/util/StateManagerImpl.java b/console/src/main/java/com/mirhoseini/marvel/util/StateManagerImpl.java new file mode 100644 index 0000000..9d0fddd --- /dev/null +++ b/console/src/main/java/com/mirhoseini/marvel/util/StateManagerImpl.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016 Karina Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ + +package com.mirhoseini.marvel.util; + +import javax.inject.Inject; + +/** + * Created by Mohsen on 06/11/2016. + */ + +public class StateManagerImpl implements StateManager { + + + @Inject + public StateManagerImpl() { + + } + + @Override + public boolean isConnect() { + return true; + } +} diff --git a/core-lib/build.gradle b/core-lib/build.gradle index aa0b10e..7465bbd 100644 --- a/core-lib/build.gradle +++ b/core-lib/build.gradle @@ -12,7 +12,7 @@ sourceSets { } } -jacocoTestReport{ +jacocoTestReport { reports { xml.enabled true html.enabled true diff --git a/core-lib/src/main/java/com/mirhoseini/marvel/base/BaseView.java b/core-lib/src/main/java/com/mirhoseini/marvel/base/BaseView.java index a6afe67..d196bec 100644 --- a/core-lib/src/main/java/com/mirhoseini/marvel/base/BaseView.java +++ b/core-lib/src/main/java/com/mirhoseini/marvel/base/BaseView.java @@ -8,6 +8,6 @@ public interface BaseView { void showMessage(String message); - void showOfflineMessage(); + void showOfflineMessage(boolean isCritical); } diff --git a/core-lib/src/main/java/com/mirhoseini/marvel/character/cache/CacheModule.java b/core-lib/src/main/java/com/mirhoseini/marvel/character/cache/CacheModule.java index 9975725..8315260 100644 --- a/core-lib/src/main/java/com/mirhoseini/marvel/character/cache/CacheModule.java +++ b/core-lib/src/main/java/com/mirhoseini/marvel/character/cache/CacheModule.java @@ -8,23 +8,11 @@ */ @Module -class CacheModule { - - private CacheView view; - - CacheModule(CacheView view) { - this.view = view; - } - - @Provides - public CacheView provideView() { - return view; - } +public class CacheModule { @Provides @Cache public CachePresenter providePresenter(CachePresenterImpl presenter) { - presenter.bind(view); return presenter; } diff --git a/core-lib/src/main/java/com/mirhoseini/marvel/character/search/SearchModule.java b/core-lib/src/main/java/com/mirhoseini/marvel/character/search/SearchModule.java index ec96f96..e1faf8e 100644 --- a/core-lib/src/main/java/com/mirhoseini/marvel/character/search/SearchModule.java +++ b/core-lib/src/main/java/com/mirhoseini/marvel/character/search/SearchModule.java @@ -8,18 +8,7 @@ */ @Module -class SearchModule { - - private SearchView view; - - SearchModule(SearchView view) { - this.view = view; - } - - @Provides - public SearchView provideView() { - return view; - } +public class SearchModule { @Provides @Search @@ -30,7 +19,6 @@ public SearchInteractor provideInteractor(SearchInteractorImpl interactor) { @Provides @Search public SearchPresenter providePresenter(SearchPresenterImpl presenter) { - presenter.bind(view); return presenter; } diff --git a/core-lib/src/main/java/com/mirhoseini/marvel/character/search/SearchPresenterImpl.java b/core-lib/src/main/java/com/mirhoseini/marvel/character/search/SearchPresenterImpl.java index ac353ea..64f3cf4 100644 --- a/core-lib/src/main/java/com/mirhoseini/marvel/character/search/SearchPresenterImpl.java +++ b/core-lib/src/main/java/com/mirhoseini/marvel/character/search/SearchPresenterImpl.java @@ -39,71 +39,68 @@ public void bind(SearchView view) { } -@Override -public void doSearch(boolean isConnected, String query, long timestamp) { - if (null != view) { - view.showProgress(); - } - - subscription = interactor.loadCharacter(query, Constants.PRIVATE_KEY, Constants.PUBLIC_KEY, timestamp) - // check if result code is OK - .map(charactersResponse -> { - if (Constants.CODE_OK == charactersResponse.getCode()) - return charactersResponse; - else - throw Exceptions.propagate(new ApiResponseCodeException(charactersResponse.getCode(), charactersResponse.getStatus())); - }) - // check if is there any result - .map(charactersResponse -> { - if (charactersResponse.getData().getCount() > 0) - return charactersResponse; - else - throw Exceptions.propagate(new NoSuchCharacterException()); - }) - // map CharacterResponse to CharacterModel - .map(Mapper::mapCharacterResponseToCharacter) - // cache data on database - .map(character -> { - try { - databaseHelper.addCharacter(character); - } catch (SQLException e) { - throw Exceptions.propagate(e); - } - - return character; - }) - .observeOn(scheduler.mainThread()) - .subscribe(character -> { - if (null != view) { - view.hideProgress(); - view.showCharacter(character); - - if (!isConnected) - view.showOfflineMessage(); - } - }, - // handle exceptions - throwable -> { - if (null != view) { - view.hideProgress(); - } - - if (isConnected) { + @Override + public void doSearch(boolean isConnected, String query, long timestamp) { + if (null != view) { + view.showProgress(); + } + + subscription = interactor.loadCharacter(query, Constants.PRIVATE_KEY, Constants.PUBLIC_KEY, timestamp) + // check if result code is OK + .map(charactersResponse -> { + if (Constants.CODE_OK == charactersResponse.getCode()) + return charactersResponse; + else + throw Exceptions.propagate(new ApiResponseCodeException(charactersResponse.getCode(), charactersResponse.getStatus())); + }) + // check if is there any result + .map(charactersResponse -> { + if (charactersResponse.getData().getCount() > 0) + return charactersResponse; + else + throw Exceptions.propagate(new NoSuchCharacterException()); + }) + // map CharacterResponse to CharacterModel + .map(Mapper::mapCharacterResponseToCharacter) + // cache data on database + .map(character -> { + try { + databaseHelper.addCharacter(character); + } catch (SQLException e) { + throw Exceptions.propagate(e); + } + + return character; + }) + .observeOn(scheduler.mainThread()) + .subscribe(character -> { if (null != view) { - if (throwable instanceof ApiResponseCodeException) - view.showServiceError((ApiResponseCodeException) throwable); - else if (throwable instanceof NoSuchCharacterException) - view.showQueryNoResult(); - else - view.showRetryMessage(throwable); + view.hideProgress(); + view.showCharacter(character); + + if (!isConnected) + view.showOfflineMessage(false); } - } else { + }, + // handle exceptions + throwable -> { if (null != view) { - view.showOfflineMessage(); + view.hideProgress(); + + if (isConnected) { + if (throwable instanceof ApiResponseCodeException) + view.showServiceError((ApiResponseCodeException) throwable); + else if (throwable instanceof NoSuchCharacterException) + view.showQueryNoResult(); + else + view.showRetryMessage(throwable); + + } else { + view.showOfflineMessage(true); + } } - } - }); -} + }); + } @Override public boolean isQueryValid(String query) { @@ -128,6 +125,5 @@ public void unbind() { interactor.unbind(); view = null; - interactor = null; } } diff --git a/core-lib/src/main/java/com/mirhoseini/marvel/domain/ClientModule.java b/core-lib/src/main/java/com/mirhoseini/marvel/domain/ClientModule.java index c3f2d1f..b948209 100644 --- a/core-lib/src/main/java/com/mirhoseini/marvel/domain/ClientModule.java +++ b/core-lib/src/main/java/com/mirhoseini/marvel/domain/ClientModule.java @@ -1,6 +1,9 @@ package com.mirhoseini.marvel.domain; +import com.mirhoseini.marvel.util.StateManager; + import java.io.File; +import java.io.IOException; import java.util.concurrent.TimeUnit; import javax.inject.Named; @@ -22,7 +25,10 @@ @Module public class ClientModule { + + private static final String HTTP_CACHE_PATH = "http-cache"; private static final String CACHE_CONTROL = "Cache-Control"; + private static final String PRAGMA = "Pragma"; @Singleton @Provides @@ -31,11 +37,13 @@ public OkHttpClient provideOkHttpClient(HttpLoggingInterceptor loggingIntercepto @Named("isDebug") boolean isDebug, Cache cache, @Named("cacheInterceptor") Interceptor cacheInterceptor, - @Named("offlineInterceptor") Interceptor offlineCacheInterceptor) { + @Named("offlineInterceptor") Interceptor offlineCacheInterceptor, + @Named("retryInterceptor") Interceptor retryInterceptor) { OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder() .addNetworkInterceptor(cacheInterceptor) .addInterceptor(offlineCacheInterceptor) + .addInterceptor(retryInterceptor) .cache(cache) .connectTimeout(networkTimeoutInSeconds, TimeUnit.SECONDS); @@ -52,7 +60,6 @@ public HttpLoggingInterceptor provideHttpLoggingInterceptor() { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BODY); return logging; - } @Provides @@ -61,7 +68,7 @@ public Cache provideCache(@Named("cacheDir") File cacheDir, @Named("cacheSize") Cache cache = null; try { - cache = new Cache(new File(cacheDir.getPath(), "http-cache"), cacheSize); + cache = new Cache(new File(cacheDir.getPath(), HTTP_CACHE_PATH), cacheSize); } catch (Exception e) { e.printStackTrace(); } @@ -81,6 +88,8 @@ public Interceptor provideCacheInterceptor(@Named("cacheMaxAge") int maxAgeMin) .build(); return response.newBuilder() + .removeHeader(PRAGMA) + .removeHeader(CACHE_CONTROL) .header(CACHE_CONTROL, cacheControl.toString()) .build(); }; @@ -89,11 +98,11 @@ public Interceptor provideCacheInterceptor(@Named("cacheMaxAge") int maxAgeMin) @Singleton @Provides @Named("offlineInterceptor") - public Interceptor provideOfflineCacheInterceptor(@Named("isConnect") boolean isConnect, @Named("cacheMaxStale") int maxStaleDay) { + public Interceptor provideOfflineCacheInterceptor(StateManager stateManager, @Named("cacheMaxStale") int maxStaleDay) { return chain -> { Request request = chain.request(); - if (!isConnect) { + if (!stateManager.isConnect()) { CacheControl cacheControl = new CacheControl.Builder() .maxStale(maxStaleDay, TimeUnit.DAYS) .build(); @@ -106,4 +115,34 @@ public Interceptor provideOfflineCacheInterceptor(@Named("isConnect") boolean is return chain.proceed(request); }; } + + @Singleton + @Provides + @Named("retryInterceptor") + public Interceptor provideRetryInterceptor(@Named("retryCount") int retryCount) { + return chain -> { + Request request = chain.request(); + Response response = null; + IOException exception = null; + + int tryCount = 0; + while (tryCount < retryCount && (null == response || !response.isSuccessful())) { + // retry the request + try { + response = chain.proceed(request); + } catch (IOException e) { + exception = e; + } finally { + tryCount++; + } + } + + // throw last exception + if (null == response && null != exception) + throw exception; + + // otherwise just pass the original response on + return response; + }; + } } diff --git a/core-lib/src/main/java/com/mirhoseini/marvel/domain/client/MarvelApi.java b/core-lib/src/main/java/com/mirhoseini/marvel/domain/client/MarvelApi.java index 55018d8..37ab088 100644 --- a/core-lib/src/main/java/com/mirhoseini/marvel/domain/client/MarvelApi.java +++ b/core-lib/src/main/java/com/mirhoseini/marvel/domain/client/MarvelApi.java @@ -12,7 +12,6 @@ */ public interface MarvelApi { - String NAME = "name"; String API_KEY = "apikey"; String HASH = "hash"; diff --git a/core-lib/src/main/java/com/mirhoseini/marvel/util/StateManager.java b/core-lib/src/main/java/com/mirhoseini/marvel/util/StateManager.java new file mode 100644 index 0000000..ea881b5 --- /dev/null +++ b/core-lib/src/main/java/com/mirhoseini/marvel/util/StateManager.java @@ -0,0 +1,11 @@ +package com.mirhoseini.marvel.util; + +/** + * Created by Mohsen on 06/11/2016. + */ + +public interface StateManager { + + boolean isConnect(); + +} diff --git a/libraries.gradle b/libraries.gradle index 49a4765..862d239 100644 --- a/libraries.gradle +++ b/libraries.gradle @@ -4,7 +4,7 @@ ext { buildToolsVersion = "25.0.0" //Android - androidSupportVersion = "25.0.1" + androidSupportVersion = "25.1.0" butterknifeVersion = "8.4.0" rxandroidVersion = "1.2.1" okhttpVersion = "3.4.1"