为什么要看Google的sample?
Google 自己写的不一定就是最好的,但是它代表了一种标准。养成一种良好的编码习惯是十分重要的。而让彼此都能看得懂对方的代码,最重要的代码简洁。而 Google 一直是简洁的代表,不是吗?
Github 地址
看这个 还有许多分支呢! 大家各取所需吧。
完成的samples
Stable samples
todo-mvp/ - Basic Model-View-Presenter architecture.
todo-mvp-loaders/ - Based on todo-mvp, fetches data using Loaders. todo-databinding/ - Based on todo-mvp, uses the Data Binding Library. todo-mvp-clean/ - Based on todo-mvp, uses concepts from Clean Architecture. todo-mvp-dagger/ - Based on todo-mvp, uses Dagger2 for Dependency Injection todo-mvp-contentproviders/ - Based on todo-mvp-loaders, fetches data using Loaders and uses Content Providers todo-mvp-rxjava/ - Based on todo-mvp, uses RxJava for concurrency and data layer abstraction.
进展中的samples
Samples in progress
dev-todo-mvp-tablet/ - Based on todo-mvp, adds a master/detail view for tablets. Also, see "New sample" issues for planned samples.
其余的分支
External samples
External samples are variants that may not be in sync with the rest of the branches.
todo-mvp-fragmentless/ - Based on todo-mvp, uses Android views instead of Fragments. todo-mvp-conductor/ - Based on todo-mvp, uses the Conductor framework to refactor to a single Activity architecture.
是不是觉得想要学的话时间不够呢?所以放弃生活中一些不重要的琐事吧。
大多数情况下选择不做什么,比选择要做什么显得更重要呢。毕竟时间有限,要做的事太多。 有时候想想,写了博客自己真的不一定会再去看一遍。但是写了博客自己真的记得更清楚了,在我看来写博客就像是对技术的告白,告诉它,你有多喜欢用它,或者多么幸运遇见她。所以写博客的时候是开心的话,为什么不写呢?哈哈。 有关,我之前觉得很 cool。看完这个 sample,确实很 cool。 下面就记录一下我在看 mvp rxjava 项目中学到的一些东西。Gradle项目配置
将依赖库版本号定义在根 build.gradle 中,因为一个复杂的项目可能存在多个 module。如果分别定义在不同的 module 中,到时候版本统一更新一定是一个头疼的问题。 所以你需要这样做: 在跟 build.gradle 中定义所需的版本号:
// Define versions in a single placeext { // Sdk and tools minSdkVersion = 10 targetSdkVersion = 22 compileSdkVersion = 23 buildToolsVersion = '23.0.2' // App dependencies supportLibraryVersion = '24.1.1' guavaVersion = '18.0' junitVersion = '4.12' mockitoVersion = '1.10.19' powerMockito = '1.6.2' hamcrestVersion = '1.3' runnerVersion = '0.4.1' rulesVersion = '0.4.1' espressoVersion = '2.2.1' rxjavaVersion = '1.1.8' rxandroidVersion = '1.2.1' sqlbriteVersion = '0.7.0'}复制代码
在 module 中引用版本号:
/* Dependency versions are defined in the top level build.gradle file. This helps keeping track of all versions in a single place. This improves readability and helps managing project complexity. */dependencies { // App's dependencies, including test compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion" compile "com.android.support:cardview-v7:$rootProject.supportLibraryVersion" compile "com.android.support:design:$rootProject.supportLibraryVersion" ...... // Resolve conflicts between main and test APK: androidTestCompile "com.android.support:support-annotations:$rootProject.supportLibraryVersion" androidTestCompile "com.android.support:support-v4:$rootProject.supportLibraryVersion" androidTestCompile "com.android.support:recyclerview-v7:$rootProject.supportLibraryVersion"}复制代码
关于RxJava
没有用过 Rxjava 的可以看看
BasePresenter
包含了订阅,和取消订阅
public interface BasePresenter { void subscribe(); void unsubscribe();}复制代码
BaseView
这是必须的,每个fragment或activity必须setPresenter吧。
public interface BaseView{ void setPresenter(T presenter);}复制代码
util
工具类,耦合性比较低,先看看。
ActivityUtils
帮助 Activity 加载 UI,定义了一个添加 Fragment 的方法。
/** * This provides methods to help Activities load their UI. */public class ActivityUtils { /** * The { @code fragment} is added to the container view with id { @code frameId}. The operation is * performed by the { @code fragmentManager}. * */ public static void addFragmentToActivity (@NonNull FragmentManager fragmentManager, @NonNull Fragment fragment, int frameId) { checkNotNull(fragmentManager); checkNotNull(fragment); FragmentTransaction transaction = fragmentManager.beginTransaction(); transaction.add(frameId, fragment); transaction.commit(); }}复制代码
EspressoIdlingResource
闲置资源的静态对象,仅仅在 build type 为 mock 时有效。这个还不是很懂,有待了解。
/** * Contains a static reference to { @link IdlingResource}, only available in the 'mock' build type. */public class EspressoIdlingResource { private static final String RESOURCE = "GLOBAL"; private static sampleCountingIdlingResource mCountingIdlingResource = new sampleCountingIdlingResource(RESOURCE); public static void increment() { mCountingIdlingResource.increment(); } public static void decrement() { mCountingIdlingResource.decrement(); } public static IdlingResource getIdlingResource() { return mCountingIdlingResource; }}复制代码
sampleCountingIdlingResource
EspressoIdlingResource,的具体实现。在访问UI的块测试中,这个类可以用来包装操作。有待了解。
public final class sampleCountingIdlingResource implements IdlingResource { private final String mResourceName; private final AtomicInteger counter = new AtomicInteger(0); // written from main thread, read from any thread. private volatile ResourceCallback resourceCallback; /** * Creates a sampleCountingIdlingResource * * @param resourceName the resource name this resource should report to Espresso. */ public sampleCountingIdlingResource(String resourceName) { mResourceName = checkNotNull(resourceName); } @Override public String getName() { return mResourceName; } @Override public boolean isIdleNow() { return counter.get() == 0; } @Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { this.resourceCallback = resourceCallback; } /** * Increments the count of in-flight transactions to the resource being monitored. */ public void increment() { counter.getAndIncrement(); } /** * Decrements the count of in-flight transactions to the resource being monitored. * * If this operation results in the counter falling below 0 - an exception is raised. * * @throws IllegalStateException if the counter is below 0. */ public void decrement() { int counterVal = counter.decrementAndGet(); if (counterVal == 0) { // we've gone from non-zero to zero. That means we're idle now! Tell espresso. if (null != resourceCallback) { resourceCallback.onTransitionToIdle(); } } if (counterVal < 0) { throw new IllegalArgumentException("Counter has been corrupted!"); } }}复制代码
schedulers 包
共有6种调度器,可以使用它灵活的切换线程。
调度器类型 | 效果 |
---|---|
Schedulers.computation() | 用于计算任务, 如事件循环或和回调处理,不要用于IO操作(IO操作请使用Schedulers.io());默认线程数等于处理器的数量 |
Schedulers.from(executor) | 使用指定的Executor作为调度器 |
Schedulers.immediate() | 在当前线程立即开始执行任务 |
Schedulers.io() | 用于IO密集型任务,如异步阻塞IO操作,这个调度器的线程池会根据需要增长;对于普通的计算任务,请使用Schedulers.computation();Schedulers.io()默认是一个CachedThreadScheduler,很像一个有线程缓存的新线程调度器 |
Schedulers.newThread() | 为每个任务创建一个新线程 |
Schedulers.trampoline() | 当其它排队的任务完成后,在当前线程排队开始执行 |
BaseSchedulerProvider
用来提供不同的调度器。
/** * Allow providing different types of { @link Scheduler}s. */public interface BaseSchedulerProvider { @NonNull Scheduler computation(); @NonNull Scheduler io(); @NonNull Scheduler ui();}复制代码
ImmediateSchedulerProvider
提供当前线程的调度器,在当前线程立即执行这些操作。
/** * Implementation of the { @link BaseSchedulerProvider} making all { @link Scheduler}s immediate. */public class ImmediateSchedulerProvider implements BaseSchedulerProvider { @NonNull @Override public Scheduler computation() { return Schedulers.immediate(); } @NonNull @Override public Scheduler io() { return Schedulers.immediate(); } @NonNull @Override public Scheduler ui() { return Schedulers.immediate(); }}复制代码
SchedulerProvider
提供不同线程的调度器用于计算,IO和UI的操作,并单例。
/** * Provides different types of schedulers. */public class SchedulerProvider implements BaseSchedulerProvider { @Nullable private static SchedulerProvider INSTANCE; // Prevent direct instantiation. private SchedulerProvider() { } public static synchronized SchedulerProvider getInstance() { if (INSTANCE == null) { INSTANCE = new SchedulerProvider(); } return INSTANCE; } @Override @NonNull public Scheduler computation() { return Schedulers.computation(); } @Override @NonNull public Scheduler io() { return Schedulers.io(); } @Override @NonNull public Scheduler ui() { return AndroidSchedulers.mainThread(); }}复制代码
数据操作
那么这个项目从哪里看比较好呢? 我喜欢从数据源开始看。 这个项目有关数据所有的操作都是通过Sqlite完成的,但是demo里用数据库模拟了一个远程的连接。
我们找到data目录。这个目录下有一个包source,和一个普通的model类Task。 source下分为local,remote,TasksDataSource接口和TasksDataSource类(TasksDataSource接口的具体实现)。TasksDataSource
支个接口定义了了数据有关的所有操作。
public interface TasksDataSource { Observable
> getTasks(); Observable getTask(@NonNull String taskId); void saveTask(@NonNull Task task); void completeTask(@NonNull Task task); void completeTask(@NonNull String taskId); void activateTask(@NonNull Task task); void activateTask(@NonNull String taskId); void clearCompletedTasks(); void refreshTasks(); void deleteAllTasks(); void deleteTask(@NonNull String taskId);}复制代码
local包
local代表本地数据。在这个包下,存放和本地数据相关的类。
首先对数据库进行基本定义。
TasksPersistenceContract对象用于存储本地数据库数据。
不需要被实例化的类,需私有化构造函数。/** * The contract used for the db to save the tasks locally. */public final class TasksPersistenceContract { // To prevent someone from accidentally instantiating the contract class, // give it an empty constructor. private TasksPersistenceContract() {} /* Inner class that defines the table contents */ /*内部类用来声明表的内容*/ public static abstract class TaskEntry implements BaseColumns { public static final String TABLE_NAME = "task"; public static final String COLUMN_NAME_ENTRY_ID = "entryid"; public static final String COLUMN_NAME_TITLE = "title"; public static final String COLUMN_NAME_DESCRIPTION = "description"; public static final String COLUMN_NAME_COMPLETED = "completed"; }}复制代码
TasksDbHelper类用于数据库的创建,升级,降级等操作。
public class TasksDbHelper extends SQLiteOpenHelper { public static final int DATABASE_VERSION = 1; public static final String DATABASE_NAME = "Tasks.db"; private static final String TEXT_TYPE = " TEXT"; private static final String BOOLEAN_TYPE = " INTEGER"; private static final String COMMA_SEP = ","; private static final String SQL_CREATE_ENTRIES = "CREATE TABLE " + TasksPersistenceContract.TaskEntry.TABLE_NAME + " (" + TasksPersistenceContract.TaskEntry._ID + TEXT_TYPE + " PRIMARY KEY," + TasksPersistenceContract.TaskEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP + TasksPersistenceContract.TaskEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP + TasksPersistenceContract.TaskEntry.COLUMN_NAME_DESCRIPTION + TEXT_TYPE + COMMA_SEP + TasksPersistenceContract.TaskEntry.COLUMN_NAME_COMPLETED + BOOLEAN_TYPE + " )"; public TasksDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ENTRIES); } public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // Not required as at version 1 } public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { // Not required as at version 1 }}复制代码
数据库相关操作
TasksLocalDataSource是TasksDataSource接口在本地数据库操作的具体实现。
这里使用到了,可以看看这篇:。SqlBrite是对 Android 系统的 SQLiteOpenHelper 的封装,对SQL操作引入了响应式语义 (Rx)(用来在 RxJava 中使用)
/** * Created by heinika on 16-11-27. * 有关数据库操作的统一管理 */public class TasksLocalDataSource implements TasksDataSource { private static TasksDataSource INSTANCE; @NonNull private final BriteDatabase mDatabaseHelper; private Func1mTaskMapperFunction; private TasksLocalDataSource(Context context, BaseSchedulerProvider schedulerProvider){ TasksDbHelper dbHelper = new TasksDbHelper(context); SqlBrite sqlBrite = SqlBrite.create(); mDatabaseHelper = sqlBrite.wrapDatabaseHelper(dbHelper,schedulerProvider.io()); mTaskMapperFunction = new Func1 (){ @Override public Task call(Cursor c) { //从数据库中取到task的数据并返回 String itemId = c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_ENTRY_ID)); String title = c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_TITLE)); String description = c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_DESCRIPTION)); boolean completed = c.getInt(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_COMPLETED)) == 1; return new Task(title, description, itemId, completed); } }; } @Override public Observable
> getTasks() { String[] projection = { TaskEntry.COLUMN_NAME_ENTRY_ID, TaskEntry.COLUMN_NAME_TITLE, TaskEntry.COLUMN_NAME_DESCRIPTION, TaskEntry.COLUMN_NAME_COMPLETED }; String sql = String.format("SELECT %s FROM %s", TextUtils.join(",", projection), TaskEntry.TABLE_NAME); return mDatabaseHelper.createQuery(TaskEntry.TABLE_NAME, sql) .mapToList(mTaskMapperFunction); } @Override public Observable getTask(@NonNull String taskId) { String[] projection = { TaskEntry.COLUMN_NAME_ENTRY_ID, TaskEntry.COLUMN_NAME_TITLE, TaskEntry.COLUMN_NAME_DESCRIPTION, TaskEntry.COLUMN_NAME_COMPLETED }; //TextUtils是Android提供的有关text的工具类 String sql = String.format("SELECT %s FROM %s WHERE %s LIKE ?", TextUtils.join(",", projection), TaskEntry.TABLE_NAME, TaskEntry.COLUMN_NAME_ENTRY_ID); return mDatabaseHelper.createQuery(TaskEntry.TABLE_NAME, sql, taskId) .mapToOneOrDefault(mTaskMapperFunction, null); } @Override public void saveTask(@NonNull Task task) { checkNotNull(task); ContentValues values = new ContentValues(); values.put(TaskEntry.COLUMN_NAME_ENTRY_ID, task.getId()); values.put(TaskEntry.COLUMN_NAME_TITLE, task.getTitle()); values.put(TaskEntry.COLUMN_NAME_DESCRIPTION, task.getDescription()); values.put(TaskEntry.COLUMN_NAME_COMPLETED, task.isCompleted()); mDatabaseHelper.insert(TaskEntry.TABLE_NAME, values, SQLiteDatabase.CONFLICT_REPLACE); } @Override public void completeTask(@NonNull Task task) { completeTask(task.getId()); } /** * 完成任务 * @param taskId 任务id */ @Override public void completeTask(@NonNull String taskId) { ContentValues values = new ContentValues(); values.put(TaskEntry.COLUMN_NAME_COMPLETED, true); String selection = TaskEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; String[] selectionArgs = {taskId}; mDatabaseHelper.update(TaskEntry.TABLE_NAME, values, selection, selectionArgs); } @Override public void activateTask(@NonNull Task task) { activateTask(task.getId()); } /** * 激活的任务 * @param taskId */ @Override public void activateTask(@NonNull String taskId) { ContentValues values = new ContentValues(); values.put(TaskEntry.COLUMN_NAME_COMPLETED, false); String selection = TaskEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; String[] selectionArgs = {taskId}; mDatabaseHelper.update(TaskEntry.TABLE_NAME, values, selection, selectionArgs); } @Override public void clearCompletedTasks() { String selection = TaskEntry.COLUMN_NAME_COMPLETED + " LIKE ?"; String[] selectionArgs = { "1"}; mDatabaseHelper.delete(TaskEntry.TABLE_NAME, selection, selectionArgs); } @Override public void refreshTasks() { // Not required because the {@link TasksRepository} handles the logic of refreshing the // tasks from all the available data sources. // 不需要,因为TasksRepository控制着所有数据刷新的逻辑 } @Override public void deleteAllTasks() { mDatabaseHelper.delete(TaskEntry.TABLE_NAME, null); } @Override public void deleteTask(@NonNull String taskId) { String selection = TaskEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; String[] selectionArgs = {taskId}; mDatabaseHelper.delete(TaskEntry.TABLE_NAME, selection, selectionArgs); }}复制代码
remote包
这个包下只有一个类TasksRemoteDataSource.java用来模拟远程地数据连接。
因为是模拟的,所以和TasksLocalDataSource.java几乎一样,只是多加了一个延时。 在现实情况下,这里用来处理各种网络连接的api。/** * Implementation of the data source that adds a latency simulating network. * 继承datasourse并添加一个模拟的有延迟的网络 */public class TasksRemoteDataSource implements TasksDataSource { private static TasksRemoteDataSource INSTANCE; private static final int SERVICE_LATENCY_IN_MILLIS = 5000; private final static MapTASKS_SERVICE_DATA; static { TASKS_SERVICE_DATA = new LinkedHashMap<>(2); addTask("Build tower in Pisa", "Ground looks good, no foundation work required."); addTask("Finish bridge in Tacoma", "Found awesome girders at half the cost!"); } public static TasksRemoteDataSource getInstance() { if (INSTANCE == null) { INSTANCE = new TasksRemoteDataSource(); } return INSTANCE; } ...... @Override public void refreshTasks() { // Not required because the {@link TasksRepository} handles the logic of refreshing the // tasks from all the available data sources. } @Override public void deleteAllTasks() { TASKS_SERVICE_DATA.clear(); } @Override public void deleteTask(@NonNull String taskId) { TASKS_SERVICE_DATA.remove(taskId); }}复制代码
TasksRepository
加载数据源到缓存的具体实现。
/** * Concrete implementation to load tasks from the data sources into a cache. * 加载数据源到缓存的具体实现 * * For simplicity, this implements a dumb synchronisation between locally persisted data and data * obtained from the server, by using the remote data source only if the local database doesn't * exist or is empty. * 举一个简单的例子,哑同步:在选择本地数据和远程服务器的数据时,只有当本地不存在或者为空时,才会从网上获取数据。 */public class TasksRepository implements TasksDataSource { @Nullable private static TasksRepository INSTANCE = null; @NonNull private final TasksDataSource mTasksRemoteDataSource; @NonNull private final TasksDataSource mTasksLocalDataSource; /** * This variable has package local visibility so it can be accessed from tests. */ @VisibleForTesting @Nullable MapmCachedTasks; /** * Marks the cache as invalid, to force an update the next time data is requested. This variable * has package local visibility so it can be accessed from tests. */ @VisibleForTesting boolean mCacheIsDirty = false; // Prevent direct instantiation. private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource, @NonNull TasksDataSource tasksLocalDataSource) { mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource); mTasksLocalDataSource = checkNotNull(tasksLocalDataSource); } /** * Returns the single instance of this class, creating it if necessary. * * @param tasksRemoteDataSource the backend data source * @param tasksLocalDataSource the device storage data source * @return the { @link TasksRepository} instance */ public static TasksRepository getInstance(@NonNull TasksDataSource tasksRemoteDataSource, @NonNull TasksDataSource tasksLocalDataSource) { if (INSTANCE == null) { INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource); } return INSTANCE; } /** * Used to force { @link #getInstance(TasksDataSource, TasksDataSource)} to create a new instance * next time it's called. */ public static void destroyInstance() { INSTANCE = null; } /** * Gets tasks from cache, local data source (SQLite) or remote data source, whichever is * available first. */ @Override public Observable
> getTasks() { // Respond immediately with cache if available and not dirty // 当cache存在立即响应 if (mCachedTasks != null && !mCacheIsDirty) { return Observable.from(mCachedTasks.values()).toList(); } else if (mCachedTasks == null) { mCachedTasks = new LinkedHashMap<>(); } Observable
> remoteTasks = getAndSaveRemoteTasks(); if (mCacheIsDirty) { return remoteTasks; } else { // Query the local storage if available. If not, query the network. Observable
> localTasks = getAndCacheLocalTasks(); return Observable.concat(localTasks, remoteTasks) .filter(new Func1
, Boolean>() { @Override public Boolean call(List tasks) { return !tasks.isEmpty(); } }).first(); } }复制代码
statistics包
用来处理和展示任务的统计信息。
StatisticsContract
这个契约类用来规定View和Presenter的接口。将这两个接口写在一起,一目了然。
/** * This specifies the contract between the view and the presenter. */public interface StatisticsContract { interface View extends BaseView{ void setProgressIndicator(boolean active); void showStatistics(int numberOfIncompleteTasks, int numberOfCompletedTasks); void showLoadingStatisticsError(); boolean isActive(); } interface Presenter extends BasePresenter { }}复制代码
StatisticsPresenter
StatisticsPresenter操作都在订阅和取消订阅中进行。通过StatisticsPresenter你可以清晰的理解整个页面在什么情况下发生了什么。
而关于UI的具体改变,并不关心。所以当UI改变时,你并不需要修改这个类,只需要修改fragment或activity就行了。/** * Listens to user actions from the UI ({ @link StatisticsFragment}), retrieves the data and updates * the UI as required. */public class StatisticsPresenter implements StatisticsContract.Presenter { @NonNull private final TasksRepository mTasksRepository; @NonNull private final StatisticsContract.View mStatisticsView; @NonNull private final BaseSchedulerProvider mSchedulerProvider; @NonNull private CompositeSubscription mSubscriptions; public StatisticsPresenter(@NonNull TasksRepository tasksRepository, @NonNull StatisticsContract.View statisticsView, @NonNull BaseSchedulerProvider schedulerProvider) { mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null"); mStatisticsView = checkNotNull(statisticsView, "statisticsView cannot be null!"); mSchedulerProvider = checkNotNull(schedulerProvider, "schedulerProvider cannot be null"); mSubscriptions = new CompositeSubscription(); mStatisticsView.setPresenter(this); } @Override public void subscribe() { loadStatistics(); } @Override public void unsubscribe() { mSubscriptions.clear(); } private void loadStatistics() { mStatisticsView.setProgressIndicator(true); // The network request might be handled in a different thread so make sure Espresso knows // that the app is busy until the response is handled. EspressoIdlingResource.increment(); // App is busy until further notice Observabletasks = mTasksRepository .getTasks() .flatMap(new Func1
, Observable >() { @Override public Observable call(List tasks) { return Observable.from(tasks); } }); Observable completedTasks = tasks.filter(new Func1 () { @Override public Boolean call(Task task) { return task.isCompleted(); } }).count(); Observable activeTasks = tasks.filter(new Func1 () { @Override public Boolean call(Task task) { return task.isActive(); } }).count(); Subscription subscription = Observable .zip(completedTasks, activeTasks, new Func2 >() { @Override public Pair call(Integer completed, Integer active) { return Pair.create(active, completed); } }) .subscribeOn(mSchedulerProvider.computation()) .observeOn(mSchedulerProvider.ui()) .doOnTerminate(new Action0() { @Override public void call() { if (!EspressoIdlingResource.getIdlingResource().isIdleNow()) { EspressoIdlingResource.decrement(); // Set app as idle. } } }) .subscribe(new Action1 >() { @Override public void call(Pair stats) { mStatisticsView.showStatistics(stats.first, stats.second); } }, new Action1 () { @Override public void call(Throwable throwable) { mStatisticsView.showLoadingStatisticsError(); } }, new Action0() { @Override public void call() { mStatisticsView.setProgressIndicator(false); } }); mSubscriptions.add(subscription); }}复制代码
StatisticsActivity
展示统计信息的activity。在这个activity中设置了toolbar和导航栏,添加了statisticsFragment。
并创建了StatisticsPresenter,初始化了StatisticsPresenter中的TasksRepository,StatisticsView,SchedulerProvider。/** * Show statistics for tasks. */public class StatisticsActivity extends AppCompatActivity { private DrawerLayout mDrawerLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.statistics_act); // Set up the toolbar. Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); ActionBar ab = getSupportActionBar(); ab.setTitle(R.string.statistics_title); ab.setHomeAsUpIndicator(R.drawable.ic_menu); ab.setDisplayHomeAsUpEnabled(true); // Set up the navigation drawer. mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerLayout.setStatusBarBackground(R.color.colorPrimaryDark); NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); if (navigationView != null) { setupDrawerContent(navigationView); } StatisticsFragment statisticsFragment = (StatisticsFragment) getSupportFragmentManager() .findFragmentById(R.id.contentFrame); if (statisticsFragment == null) { statisticsFragment = StatisticsFragment.newInstance(); ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), statisticsFragment, R.id.contentFrame); } new StatisticsPresenter( Injection.provideTasksRepository(getApplicationContext()), statisticsFragment, Injection.provideSchedulerProvider()); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: // Open the navigation drawer when the home icon is selected from the toolbar. mDrawerLayout.openDrawer(GravityCompat.START); return true; } return super.onOptionsItemSelected(item); } private void setupDrawerContent(NavigationView navigationView) { navigationView.setNavigationItemSelectedListener( new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem menuItem) { switch (menuItem.getItemId()) { case R.id.list_navigation_menu_item: Intent intent = new Intent(StatisticsActivity.this, TasksActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); break; case R.id.statistics_navigation_menu_item: // Do nothing, we're already on that screen break; default: break; } // Close the navigation drawer when an item is selected. menuItem.setChecked(true); mDrawerLayout.closeDrawers(); return true; } }); }}复制代码
StatisticsFragment
StatisticsFragment 实现了 StatisticsContract.View 接口,在 setPresenter 方法中传入 Presenter,并在其他方法中实现 UI 布局变化。
传入的 presenter 中的 subscribe() 方法和 unsubscribe() 需要分别在 onResume() 和 onPause() 调用,用来获取或释放资源。/** * Main UI for the statistics screen. */public class StatisticsFragment extends Fragment implements StatisticsContract.View { private TextView mStatisticsTV; private StatisticsContract.Presenter mPresenter; public static StatisticsFragment newInstance() { return new StatisticsFragment(); } @Override public void setPresenter(@NonNull StatisticsContract.Presenter presenter) { mPresenter = checkNotNull(presenter); } @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.statistics_frag, container, false); mStatisticsTV = (TextView) root.findViewById(R.id.statistics); return root; } @Override public void onResume() { super.onResume(); mPresenter.subscribe(); } @Override public void onPause() { super.onPause(); mPresenter.unsubscribe(); } @Override public void setProgressIndicator(boolean active) { if (active) { mStatisticsTV.setText(getString(R.string.loading)); } } @Override public void showStatistics(int numberOfIncompleteTasks, int numberOfCompletedTasks) { if (numberOfCompletedTasks == 0 && numberOfIncompleteTasks == 0) { mStatisticsTV.setText(getResources().getString(R.string.statistics_no_tasks)); } else { String displayString = getResources().getString(R.string.statistics_active_tasks) + " " + numberOfIncompleteTasks + "\n" + getResources().getString( R.string.statistics_completed_tasks) + " " + numberOfCompletedTasks; mStatisticsTV.setText(displayString); } } @Override public void showLoadingStatisticsError() { mStatisticsTV.setText(getResources().getString(R.string.statistics_error)); } @Override public boolean isActive() { return isAdded(); }}复制代码
剩下的包
还剩下 addedittask,taskdetail,tasks 这几个包。他们的结构和 statistics 相同,就不在描述了。
相关的测试
这个sample对测试还是相当重视的,有需要的可以去学习一下。 个人博客地址:http://heinika.coding.me/2016/12/06/MyBlog/LearnFromMvpRxjavaSimple/