Google Rx Java+MVP Sample 보여드릴게요.
Google의 Sample을 보는 이유는 무엇입니까?
Google이 직접 작성한 것이 반드시 가장 좋은 것은 아니지만 표준을 나타냅니다.좋은 인코딩 습관을 기르는 것은 매우 중요하다.서로의 코드를 알아볼 수 있도록 가장 중요한 코드는 간결하다.Google은 항상 간결한 대표가 아닙니까?
Github 주소
이 Sample 프로젝트를 보면 아직도 많은 지점이 있다!모두 각자 필요한 것을 취하세요.
완성된 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에는 복잡한 항목에 여러 개의 모듈이 존재할 수 있기 때문입니다.만약 각각 다른 모듈에 정의된다면, 그때 버전을 통일적으로 업데이트하는 것은 틀림없이 골치 아픈 문제일 것이다.그래서 너는 이렇게 해야 한다:build과.gradle에서 원하는 버전 번호를 정의합니다.
// Define versions in a single place
ext {
// 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
EspressoIdling Resource의 구체적인 구현.이 클래스는 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()
입출력 집약형 작업에 사용되며, 예를 들어 비동기적으로 입출력 작업을 막으면 이 스케줄러의 스레드 탱크는 수요에 따라 증가합니다.일반적인 계산 작업의 경우 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를 통해 이루어졌지만 데모에서는 데이터베이스로 원격 연결을 시뮬레이션했다.데이터 디렉터리를 찾았습니다.이 디렉터리에는 패키지 소스와 일반 모델 클래스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 인터페이스가 로컬 데이터베이스에서 조작되는 구체적인 실현이다.여기에 SQL Brite를 사용했습니다. 이 편을 보십시오. 여기 찌르세요.
SqlBrite는 안드로이드 시스템의 SQLiteOpenHelper를 봉인하고 SQL 작업에 응답식 의미를 도입합니다 (Rx 자바에서 사용)
/**
* Created by heinika on 16-11-27.
*
*/
public class TasksLocalDataSource implements TasksDataSource {
private static TasksDataSource INSTANCE;
@NonNull
private final BriteDatabase mDatabaseHelper;
private Func1 mTaskMapperFunction;
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 패키지
이 가방 아래에는 Tasks Remote DataSource 클래스가 하나밖에 없습니다.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 Map TASKS_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
Map mCachedTasks;
/**
* 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
Observable tasks = 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에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 + "
" + 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/
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.