├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── 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 │ │ │ │ ├── attrs.xml │ │ │ │ ├── ids.xml │ │ │ │ ├── strings.xml │ │ │ │ ├── styles.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── colors.xml │ │ │ ├── drawable │ │ │ │ └── chip.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ └── layout │ │ │ │ ├── main_activity.xml │ │ │ │ ├── pokedex_item_loading.xml │ │ │ │ ├── pokemon_chip.xml │ │ │ │ ├── pokemon_table_card.xml │ │ │ │ ├── pokedex_item.xml │ │ │ │ ├── pokemon_titleview.xml │ │ │ │ ├── pokedex_item_error.xml │ │ │ │ ├── include_error.xml │ │ │ │ ├── pokedex_fragment.xml │ │ │ │ └── pokemon_fragment.xml │ │ ├── java │ │ │ └── me │ │ │ │ └── tatarka │ │ │ │ └── pokemvvm │ │ │ │ ├── viewmodel │ │ │ │ ├── State.java │ │ │ │ └── ErrorViewModel.java │ │ │ │ ├── pokedex │ │ │ │ ├── PokedexComponent.java │ │ │ │ ├── PokedexRetainedComponent.java │ │ │ │ ├── LoadingItemViewModel.java │ │ │ │ ├── ErrorItemViewModel.java │ │ │ │ ├── PokemonItemViewModel.java │ │ │ │ ├── PagedResult.java │ │ │ │ ├── PokedexFragment.java │ │ │ │ ├── PokedexPager.java │ │ │ │ └── PokedexViewModel.java │ │ │ │ ├── pokemon │ │ │ │ ├── PokemonComponent.java │ │ │ │ ├── Row.java │ │ │ │ ├── Chip.java │ │ │ │ ├── SlideDownAndFadeOut.java │ │ │ │ ├── BindingAdaptors.java │ │ │ │ ├── SlideAndFadeAnimatorUtil.java │ │ │ │ ├── AnchorAppBarSlide.java │ │ │ │ ├── ChipLayout.java │ │ │ │ ├── AppBarAnimator.java │ │ │ │ ├── TableCardLayout.java │ │ │ │ ├── PokemonFragment.java │ │ │ │ ├── TitleView.java │ │ │ │ └── PokemonViewModel.java │ │ │ │ ├── log │ │ │ │ ├── EmptyTree.java │ │ │ │ └── LogModule.java │ │ │ │ ├── dagger │ │ │ │ ├── ViewScope.java │ │ │ │ ├── RetainedScope.java │ │ │ │ ├── SingletonComponent.java │ │ │ │ └── Dagger.java │ │ │ │ ├── api │ │ │ │ ├── PokeService.java │ │ │ │ ├── PokemonItem.java │ │ │ │ ├── Page.java │ │ │ │ ├── HttpUrlTypeAdapter.java │ │ │ │ ├── CachingInterceptor.java │ │ │ │ ├── RxPagedLoader.java │ │ │ │ ├── ApiModule.java │ │ │ │ └── Pokemon.java │ │ │ │ ├── databinding │ │ │ │ ├── RVUtils.java │ │ │ │ ├── BindingAdapters.java │ │ │ │ └── RecyclerViewBindingAdapters.java │ │ │ │ ├── util │ │ │ │ └── StringUtils.java │ │ │ │ ├── BaseActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ └── BaseFragment.java │ │ └── AndroidManifest.xml │ ├── debug │ │ └── res │ │ │ └── drawable-mdpi │ │ │ └── bulbasaur.png │ └── test │ │ └── java │ │ └── me │ │ └── tatarka │ │ └── pokemvvm │ │ └── pokedex │ │ ├── PokemonViewModelTest.java │ │ ├── PokedexViewModelTest.java │ │ └── PokedexPagerTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── README.md ├── gradlew.bat ├── gradlew └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/ 4 | .DS_Store 5 | /build 6 | /captures 7 | *.iml 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evant/PokeMVVM/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evant/PokeMVVM/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evant/PokeMVVM/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/debug/res/drawable-mdpi/bulbasaur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evant/PokeMVVM/HEAD/app/src/debug/res/drawable-mdpi/bulbasaur.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evant/PokeMVVM/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evant/PokeMVVM/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evant/PokeMVVM/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/viewmodel/State.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.viewmodel; 2 | 3 | public enum State { 4 | LOADING, LOADED, ERROR 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/viewmodel/ErrorViewModel.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.viewmodel; 2 | 3 | /** 4 | * A view model that can have an error state. 5 | */ 6 | public interface ErrorViewModel { 7 | 8 | void retry(); 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Sep 10 16:54:24 EDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/chip.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/pokedex/PokedexComponent.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.pokedex; 2 | 3 | import dagger.Subcomponent; 4 | import me.tatarka.pokemvvm.dagger.ViewScope; 5 | 6 | @ViewScope 7 | @Subcomponent 8 | public interface PokedexComponent { 9 | void inject(PokedexFragment fragment); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/pokedex/PokedexRetainedComponent.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.pokedex; 2 | 3 | import dagger.Subcomponent; 4 | import me.tatarka.pokemvvm.dagger.RetainedScope; 5 | 6 | @RetainedScope 7 | @Subcomponent 8 | public interface PokedexRetainedComponent { 9 | PokedexComponent pokedex(); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/pokemon/PokemonComponent.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.pokemon; 2 | 3 | import dagger.Subcomponent; 4 | import me.tatarka.pokemvvm.dagger.ViewScope; 5 | 6 | @ViewScope 7 | @Subcomponent 8 | public interface PokemonComponent { 9 | void inject(PokemonFragment fragment); 10 | 11 | void inject(TitleView view); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/log/EmptyTree.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.log; 2 | 3 | import timber.log.Timber; 4 | 5 | /** 6 | * A tree that does nothing, useful for production or tests. 7 | */ 8 | public class EmptyTree extends Timber.Tree { 9 | @Override 10 | protected void log(int priority, String tag, String message, Throwable t) { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PokeMVVM 3 | Error Loading Data 4 | Retry 5 | 6 | #%1$s %2$s 7 | HT: %1$d WT: %2$d 8 | Stats 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/dagger/ViewScope.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.dagger; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | /** 9 | * Scoped to the same lifecycle as view. It will be recreated on configuration changes. 10 | */ 11 | @Scope 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface ViewScope { 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/dagger/RetainedScope.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.dagger; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | /** 9 | * Scoped to it's parent (activity/fragment) but retained across configuration changes. 10 | */ 11 | @Scope 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface RetainedScope { 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/api/PokeService.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.api; 2 | 3 | import retrofit2.http.GET; 4 | import retrofit2.http.Url; 5 | import rx.Single; 6 | 7 | public interface PokeService { 8 | @GET("pokemon") 9 | Single> firstPokemonPage(); 10 | 11 | @GET 12 | Single> nextPokemonPage(@Url String url); 13 | 14 | @GET 15 | Single pokemon(@Url String url); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 16dp 7 | 60dp 8 | 24dp 9 | 10 | 64dp 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pokedex_item_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/databinding/RVUtils.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.databinding; 2 | 3 | import android.support.v7.widget.LinearLayoutManager; 4 | import android.support.v7.widget.RecyclerView; 5 | 6 | public class RVUtils { 7 | 8 | public static int lastVisibleItemPosition(RecyclerView recyclerView) { 9 | LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 10 | return layoutManager.findLastVisibleItemPosition(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/pokemon/Row.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.pokemon; 2 | 3 | public class Row { 4 | 5 | private final CharSequence name; 6 | private final CharSequence value; 7 | 8 | public Row(CharSequence name, CharSequence value) { 9 | this.name = name; 10 | this.value = value; 11 | } 12 | 13 | public CharSequence name() { 14 | return name; 15 | } 16 | 17 | public CharSequence value() { 18 | return value; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/pokemon/Chip.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.pokemon; 2 | 3 | import android.support.annotation.ColorInt; 4 | 5 | public class Chip { 6 | @ColorInt 7 | private final int color; 8 | private final CharSequence text; 9 | 10 | public Chip(int color, CharSequence text) { 11 | this.color = color; 12 | this.text = text; 13 | } 14 | 15 | public int color() { 16 | return color; 17 | } 18 | 19 | public CharSequence text() { 20 | return text; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.util; 2 | 3 | import android.support.annotation.Nullable; 4 | import android.text.TextUtils; 5 | 6 | public class StringUtils { 7 | public static String capitalize(@Nullable String string) { 8 | if (TextUtils.isEmpty(string)) { 9 | return string; 10 | } else { 11 | char ch = string.charAt(0); 12 | return Character.isTitleCase(ch) ? string : Character.toTitleCase(ch) + string.substring(1); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/log/LogModule.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.log; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import me.tatarka.pokemvvm.BuildConfig; 8 | import timber.log.Timber; 9 | 10 | @Module 11 | public class LogModule { 12 | 13 | @Provides 14 | @Singleton 15 | public Timber.Tree providesTree() { 16 | if (BuildConfig.DEBUG) { 17 | return new Timber.DebugTree(); 18 | } else { 19 | return new EmptyTree(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pokemon_chip.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/pokedex/LoadingItemViewModel.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.pokedex; 2 | 3 | import me.tatarka.bindingcollectionadapter.ItemBinding; 4 | import me.tatarka.bindingcollectionadapter.itembindings.ItemBindingModel; 5 | import me.tatarka.pokemvvm.R; 6 | 7 | /** 8 | * Represents an item that just shows a loading indicator. 9 | */ 10 | public class LoadingItemViewModel implements ItemBindingModel { 11 | @Override 12 | public void onItemBind(ItemBinding itemBinding) { 13 | itemBinding.set(ItemBinding.VAR_NONE, R.layout.pokedex_item_loading); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/databinding/BindingAdapters.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.databinding; 2 | 3 | import android.databinding.BindingAdapter; 4 | import android.view.View; 5 | 6 | /** 7 | * This class is only for general binding adapters. If you have some specific to some part 8 | * of the app it should go in it's respective package. 9 | */ 10 | public class BindingAdapters { 11 | 12 | @BindingAdapter("android:visibility") 13 | public static void setVisible(View view, boolean visible) { 14 | view.setVisibility(visible ? View.VISIBLE : View.GONE); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/dagger/SingletonComponent.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.dagger; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.Component; 6 | import me.tatarka.pokemvvm.api.ApiModule; 7 | import me.tatarka.pokemvvm.log.LogModule; 8 | import me.tatarka.pokemvvm.pokedex.PokedexRetainedComponent; 9 | import me.tatarka.pokemvvm.pokemon.PokemonComponent; 10 | 11 | @Singleton 12 | @Component(modules = {ApiModule.class, LogModule.class}) 13 | public interface SingletonComponent { 14 | 15 | PokedexRetainedComponent retainedPokedex(); 16 | 17 | PokemonComponent pokemon(); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pokemon_table_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/pokemon/SlideDownAndFadeOut.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.pokemon; 2 | 3 | import android.animation.Animator; 4 | import android.transition.TransitionValues; 5 | import android.transition.Visibility; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | public class SlideDownAndFadeOut extends Visibility { 10 | 11 | @Override 12 | public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { 13 | if (startValues == null) { 14 | return null; 15 | } 16 | return SlideAndFadeAnimatorUtil.hide(view); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /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 /opt/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/main/java/me/tatarka/pokemvvm/api/PokemonItem.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.api; 2 | 3 | import android.os.Parcelable; 4 | 5 | import java.util.List; 6 | 7 | import auto.parcel.AutoParcel; 8 | import me.tatarka.gsonvalue.annotations.GsonConstructor; 9 | import okhttp3.HttpUrl; 10 | 11 | @AutoParcel 12 | public abstract class PokemonItem implements Parcelable { 13 | 14 | @GsonConstructor 15 | public static PokemonItem create(String name, HttpUrl url) { 16 | return new AutoParcel_PokemonItem(name, url); 17 | } 18 | 19 | public abstract String name(); 20 | 21 | public abstract HttpUrl url(); 22 | 23 | public String number() { 24 | List pathSegments = url().pathSegments(); 25 | return pathSegments.get(pathSegments.size() - 2); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/api/Page.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.api; 2 | 3 | import android.support.annotation.Nullable; 4 | 5 | import java.util.List; 6 | 7 | import auto.parcel.AutoParcel; 8 | import me.tatarka.gsonvalue.annotations.GsonConstructor; 9 | import okhttp3.HttpUrl; 10 | 11 | @AutoParcel 12 | public abstract class Page { 13 | 14 | @GsonConstructor 15 | public static Page create(int count, @Nullable HttpUrl next, @Nullable HttpUrl previous, List results) { 16 | return new AutoParcel_Page<>(count, next, previous, results); 17 | } 18 | 19 | public abstract int count(); 20 | 21 | @Nullable 22 | public abstract HttpUrl next(); 23 | 24 | @Nullable 25 | public abstract HttpUrl previous(); 26 | 27 | public abstract List results(); 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pokedex_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/pokedex/ErrorItemViewModel.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.pokedex; 2 | 3 | import com.android.databinding.library.baseAdapters.BR; 4 | 5 | import me.tatarka.bindingcollectionadapter.ItemBinding; 6 | import me.tatarka.bindingcollectionadapter.itembindings.ItemBindingModel; 7 | import me.tatarka.pokemvvm.R; 8 | 9 | public class ErrorItemViewModel implements ItemBindingModel { 10 | 11 | private final OnRetryListener listener; 12 | 13 | public ErrorItemViewModel(OnRetryListener listener) { 14 | this.listener = listener; 15 | } 16 | 17 | public void retry() { 18 | listener.retry(); 19 | } 20 | 21 | @Override 22 | public void onItemBind(ItemBinding itemBinding) { 23 | itemBinding.set(BR.item, R.layout.pokedex_item_error); 24 | } 25 | 26 | public interface OnRetryListener { 27 | void retry(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/pokemon/BindingAdaptors.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.pokemon; 2 | 3 | import android.databinding.BindingAdapter; 4 | import android.view.View; 5 | 6 | public class BindingAdaptors { 7 | 8 | @BindingAdapter({"android:visibility", "slideAndFade"}) 9 | public static void setVisibleAnimated(final View view, boolean visible, boolean slideAndFade) { 10 | if (!slideAndFade) { 11 | view.setVisibility(visible ? View.VISIBLE : View.GONE); 12 | return; 13 | } 14 | int currentVisibility = view.getVisibility(); 15 | if (currentVisibility == (visible ? View.VISIBLE : View.GONE)) { 16 | return; 17 | } 18 | if (visible) { 19 | view.setVisibility(View.VISIBLE); 20 | SlideAndFadeAnimatorUtil.show(view).start(); 21 | } else { 22 | view.setVisibility(View.GONE); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/api/HttpUrlTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.api; 2 | 3 | import com.google.gson.TypeAdapter; 4 | import com.google.gson.stream.JsonReader; 5 | import com.google.gson.stream.JsonToken; 6 | import com.google.gson.stream.JsonWriter; 7 | 8 | import java.io.IOException; 9 | 10 | import okhttp3.HttpUrl; 11 | 12 | public class HttpUrlTypeAdapter extends TypeAdapter { 13 | @Override 14 | public void write(JsonWriter out, HttpUrl value) throws IOException { 15 | if (value == null) { 16 | out.nullValue(); 17 | } else { 18 | out.value(value.toString()); 19 | } 20 | } 21 | 22 | @Override 23 | public HttpUrl read(JsonReader in) throws IOException { 24 | if (in.peek() == JsonToken.NULL) { 25 | in.nextNull(); 26 | return null; 27 | } else { 28 | return HttpUrl.parse(in.nextString()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | import me.tatarka.retainstate.RetainState; 8 | 9 | public class BaseActivity extends AppCompatActivity implements RetainState.Provider { 10 | private RetainState retainState; 11 | 12 | @Override 13 | protected void onCreate(@Nullable Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | retainState = new RetainState(getLastCustomNonConfigurationInstance()); 16 | } 17 | 18 | @Override 19 | public Object onRetainCustomNonConfigurationInstance() { 20 | return retainState.getState(); 21 | } 22 | 23 | @Override 24 | public RetainState getRetainState() { 25 | if (retainState == null) { 26 | throw new IllegalStateException("RetainState has not yet been initialized"); 27 | } 28 | return retainState; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PokeMVVM 2 | A playground for MVVM style architecture on Android 3 | 4 | This architecture relies heavily on android databinding, but requires _nothing_ else not provided by the framework. You split up you app into the following components: 5 | 6 | ## Model 7 | Holds are your data and buisness logic. You shouldn't need any of the android fragmework for this. 8 | 9 | ## View 10 | Includes your layout files and any custom views you need to create. Have a very simple lifecycle of being created and destroyed on configuration changes. You use databinding to connect this to your view model. 11 | 12 | ## View Model 13 | Includes are the logic to display your models and respond to user events. These have the same lifecycle of views. 14 | 15 | ## Api/Database 16 | These obtain and store your data, often asynchrnously, but don't care about Android's lifecycle. 17 | 18 | ## Activity/Fragments 19 | Used to coordinate the above components and deal with lifecycle events. May use loaders to get data from the api into the view model. Be careful with these, as soon as you need to do anything more complex than simple coordination, move it into it's own class. 20 | -------------------------------------------------------------------------------- /app/src/main/java/me/tatarka/pokemvvm/pokemon/SlideAndFadeAnimatorUtil.java: -------------------------------------------------------------------------------- 1 | package me.tatarka.pokemvvm.pokemon; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorSet; 5 | import android.animation.ObjectAnimator; 6 | import android.view.View; 7 | 8 | import me.tatarka.pokemvvm.R; 9 | 10 | public class SlideAndFadeAnimatorUtil { 11 | 12 | public static Animator show(View view) { 13 | int translationAmount = view.getResources().getDimensionPixelOffset(R.dimen.anim_translation); 14 | AnimatorSet set = new AnimatorSet(); 15 | ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1); 16 | ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, translationAmount, 0); 17 | set.playTogether(alpha, translationY); 18 | return set; 19 | } 20 | 21 | public static Animator hide(View view) { 22 | int translationAmount = view.getResources().getDimensionPixelOffset(R.dimen.anim_translation); 23 | AnimatorSet set = new AnimatorSet(); 24 | ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0); 25 | ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, 0, translationAmount); 26 | set.playTogether(alpha, translationY); 27 | return set; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pokemon_titleview.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 13 | 19 | 20 | 26 | 27 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #424242 4 | #212121 5 | #d50000 6 | #ffffff 7 | 8 | #f44336 9 | #e91e63 10 | #ff80ab 11 | #9c27b0 12 | #673ab7 13 | #3f51b5 14 | #2196f3 15 | #00bcd4 16 | #009688 17 | #4caf50 18 | #8bc34a 19 | #cddc39 20 | #ffeb3b 21 | #ff9800 22 | #ff5722 23 | #795548 24 | #9e9e9e 25 | #607d8b 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pokedex_item_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 15 | 16 | 25 | 26 |