2015-12-16 15 views
42

Tôi đã chơi xung quanh với mẫu MVP trong một vài tuần và tôi đã đến thời điểm tôi cần ngữ cảnh để bắt đầu một số service và truy cập Shared Preferences.Người trình bày có kiến ​​thức về Hoạt động/Bối cảnh là một ý tưởng tồi trong mẫu MVP không?

Tôi đã đọc rằng mục đích của MVP là tách chế độ xem khỏi logic và có context trong phạm vi Presenter có thể đánh bại mục đích đó (sửa tôi nếu tôi sai về điều này).

Hiện nay, tôi có một LoginActivity mà trông giống như sau:

LoginActivity.java

public class LoginActivity extends Activity implements ILoginView { 

    private final String LOG_TAG = "LOGIN_ACTIVITY"; 

    @Inject 
    ILoginPresenter mPresenter; 
    @Bind(R.id.edit_login_password) 
    EditText editLoginPassword; 
    @Bind(R.id.edit_login_username) 
    EditText editLoginUsername; 
    @Bind(R.id.progress) 
    ProgressBar mProgressBar; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_login); 
     MyApplication.getObjectGraphPresenters().inject(this); 
     mPresenter.setLoginView(this, getApplicationContext()); 
    } 

    @Override 
    public void onStart() { 
     mPresenter.onStart(); 
     ButterKnife.bind(this); 
     super.onStart(); 
    } 

    @Override 
    public void onResume() { 
     mPresenter.onResume(); 
     super.onResume(); 
    } 

    @Override 
    public void onPause() { 
     mPresenter.onPause(); 
     super.onPause(); 
    } 

    @Override 
    public void onStop() { 
     mPresenter.onStop(); 
     super.onStop(); 
    } 

    @Override 
    public void onDestroy() { 
     ButterKnife.unbind(this); 
     super.onDestroy(); 
    } 

    @OnClick(R.id.button_login) 
    public void onClickLogin(View view) { 
     mPresenter.validateCredentials(editLoginUsername.getText().toString(), 
       editLoginPassword.getText().toString()); 
    } 

    @Override public void showProgress() { mProgressBar.setVisibility(View.VISIBLE); } 

    @Override public void hideProgress() { 
     mProgressBar.setVisibility(View.GONE); 
    } 

    @Override public void setUsernameError() { editLoginUsername.setError("Username Error"); } 

    @Override public void setPasswordError() { editLoginPassword.setError("Password Error"); } 

    @Override public void navigateToHome() { 
     startActivity(new Intent(this, HomeActivity.class)); 
     finish(); 
    } 
} 

Presenter Interface ILoginPresenter.java

public interface ILoginPresenter { 
    public void validateCredentials(String username, String password); 


    public void onUsernameError(); 

    public void onPasswordError(); 

    public void onSuccess(LoginEvent event); 

    public void setLoginView(ILoginView loginView, Context context); 

    public void onResume(); 

    public void onPause(); 

    public void onStart(); 

    public void onStop(); 
} 

Cuối cùng, trình bày của tôi :

LoginPresenterImpl.java

public class LoginPresenterImpl implements ILoginPresenter { 

    @Inject 
    Bus bus; 

    private final String LOG_TAG = "LOGIN_PRESENTER"; 
    private ILoginView loginView; 
    private Context context; 
    private LoginInteractorImpl loginInteractor; 

    public LoginPresenterImpl() { 
     MyApplication.getObjectGraph().inject(this); 
     this.loginInteractor = new LoginInteractorImpl(); 
    } 

    /** 
    * This method is set by the activity so that way we have context of the interface 
    * for the activity while being able to inject this presenter into the activity. 
    * 
    * @param loginView 
    */ 
    @Override 
    public void setLoginView(ILoginView loginView, Context context) { 
     this.loginView = loginView; 
     this.context = context; 

     if(SessionUtil.isLoggedIn(this.context)) { 
      Log.i(LOG_TAG, "User logged in already"); 
      this.loginView.navigateToHome(); 
     } 
    } 

    @Override 
    public void validateCredentials(String username, String password) { 
     loginView.showProgress(); 
     loginInteractor.login(username, password, this); 
    } 

    @Override 
    public void onUsernameError() { 
     loginView.setUsernameError(); 
     loginView.hideProgress(); 
    } 

    @Override 
    public void onPasswordError() { 
     loginView.setPasswordError(); 
     loginView.hideProgress(); 
    } 

    @Subscribe 
    @Override 
    public void onSuccess(LoginEvent event) { 
     if (event.getIsSuccess()) { 
      SharedPreferences.Editor editor = 
        context.getSharedPreferences(SharedPrefs.LOGIN_PREFERENCES 
          .isLoggedIn, 0).edit(); 
      editor.putString("logged_in", "true"); 
      editor.commit(); 

      loginView.navigateToHome(); 
      loginView.hideProgress(); 
     } 
    } 

    @Override 
    public void onStart() { 
     bus.register(this); 
    } 

    @Override 
    public void onStop() { 
     bus.unregister(this); 

    } 

    @Override 
    public void onPause() { 

    } 

    @Override 
    public void onResume() { 
    } 
} 

Như bạn thấy đấy, tôi đã thông qua bối cảnh từ Activity vào Presenter của tôi chỉ để tôi có thể truy cập vào Shared Preferences. Tôi khá lo lắng về việc chuyển ngữ cảnh vào người trình bày của tôi. Đây có phải là một điều tốt để làm? Hay tôi nên làm theo cách khác?

EDIT thực hiện ưu tiên thứ 3 Jahnold của

Vì vậy, hãy bỏ qua các giao diện và thực hiện bởi vì nó khá nhiều toàn bộ điều. Vì vậy, bây giờ tôi là injecting giao diện cho Sharedpreference vào người trình bày của tôi. Dưới đây là mã của tôi cho AppModule

AppModule.java

@Module(library = true, 
    injects = { 
      LoginInteractorImpl.class, 
      LoginPresenterImpl.class, 
      HomeInteractorImpl.class, 
      HomePresenterImpl.class, 

    } 
) 
public class AppModule { 

    private MyApplication application; 

    public AppModule(MyApplication application) { 
     this.application = application; 
    } 

    @Provides 
    @Singleton 
    public RestClient getRestClient() { 
     return new RestClient(); 
    } 

    @Provides 
    @Singleton 
    public Bus getBus() { 
     return new Bus(ThreadEnforcer.ANY); 
    } 

    @Provides 
    @Singleton 
    public ISharedPreferencesRepository getSharedPreferenceRepository() { return new SharedPreferencesRepositoryImpl(application.getBaseContext()); } 

    } 
} 

Con đường tôi lấy bối cảnh là từ MyApplication.java

Khi ứng dụng bắt đầu, tôi chắc chắn rằng để tạo ra đồ thị đối tượng này với dòng mã này:

objectGraph = ObjectGraph.create(new AppModule(this)); 

Điều này có được không? Ý tôi là bây giờ tôi không phải chuyển ngữ cảnh từ hoạt động này sang người trình bày của tôi, nhưng tôi vẫn có ngữ cảnh của ứng dụng.

Trả lời

57

Đã một thời gian kể từ khi bạn đặt câu hỏi này nhưng tôi nghĩ sẽ rất hữu ích khi cung cấp câu trả lời. Tôi mạnh mẽ đề nghị rằng người trình bày không nên có khái niệm về bối cảnh Android (hoặc bất kỳ lớp học Android nào khác). Bằng cách tách hoàn toàn mã trình bày của bạn khỏi mã hệ thống Android, bạn có thể kiểm tra nó trên JVM mà không có sự phức tạp của các thành phần hệ thống nhạo báng.

Để đạt được điều này, tôi nghĩ bạn có ba tùy chọn.

SharedPreferences Truy cập từ View

Đây là yêu thích nhất của tôi trong ba như truy cập SharedPreferences là không một hành động xem. Tuy nhiên nó giữ mã hệ thống Android trong Activity khỏi Presenter. Trong giao diện xem của bạn có một phương thức:

boolean isLoggedIn(); 

có thể được gọi từ người trình bày.

SharedPreferences Tiêm dùng Dagger

Như bạn đã sử dụng Dagger để bơm xe buýt trường hợp bạn có thể thêm SharedPreferences để ObjectGraph của bạn và như vậy sẽ nhận được một trường hợp SharedPreferences đó đã được xây dựng bằng cách sử dụng ApplicationContext. Đây là bạn nhận được chúng mà không cần phải vượt qua một bối cảnh vào người trình bày của bạn.

Nhược điểm của phương pháp này là bạn vẫn đang chuyển sang lớp hệ thống Android (SharedPreferences) và sẽ phải giả lập nó khi bạn muốn kiểm tra Trình bày.

Tạo một giao diện SharePreferencesRepository

Đây là phương pháp ưa thích của tôi cho việc truy cập dữ liệu SharedPreferences từ bên trong một trình bày. Về cơ bản bạn xử lý SharedPreferences như là một mô hình và có một giao diện kho lưu trữ cho nó.

giao diện của bạn sẽ tương tự như:

public interface SharedPreferencesRepository { 

    boolean isLoggedIn(); 
} 

Sau đó bạn có thể có một thực hiện cụ thể của việc này:

public class SharedPreferencesRepositoryImpl implements SharedPreferencesRepository { 

    private SharedPreferences prefs; 

    public SharedPreferencesRepositoryImpl(Context context) { 

     prefs = PreferenceManager.getDefaultSharedPreferences(context); 
    } 

    @Override 
    public boolean isLoggedIn() { 

     return prefs.getBoolean(Constants.IS_LOGGED_IN, false); 
    } 

} 

Đây là giao diện SharedPreferencesRepository mà bạn sau đó tiêm với Dagger vào Presenter của bạn. Bằng cách này, một mô hình rất đơn giản có thể được cung cấp trong thời gian chạy trong khi kiểm tra. Trong hoạt động bình thường, việc thực hiện cụ thể được cung cấp.

+0

Tôi thích cái cuối cùng, nhưng nhìn vào việc triển khai, tôi vẫn cần ngữ cảnh đúng không? Vì vậy, bên trong mô-đun DI của tôi, tôi phải xác định một nơi nào đó bối cảnh chính xác? Tôi hỏi điều này bởi vì tôi không biết làm thế nào để thiết lập tiêm lên này. Ngoài ra, có thể thực hiện tương tự với các dịch vụ không? –

+0

Nevermind, tôi đã tìm ra. Làm việc tốt nhưng tôi không chắc chắn làm thế nào điều này là làm việc đằng sau hậu trường. Tôi sẽ cập nhật câu hỏi của tôi để cho bạn thấy những gì tôi đã làm để thực hiện tiêm và cho tôi biết nếu điều này không phải là tối ưu. –

+0

Điều bạn đã làm có vẻ ổn. Ngữ cảnh ứng dụng hiện bị ẩn khỏi Trình bày vì nó được đóng gói trong SharedPreferencesRepository. Tất cả các Presenter biết về là kho lưu trữ. – Jahnold

3

Câu hỏi này đã được trả lời một thời gian trước đây, và, giả định rằng định nghĩa MVP là những gì OP được sử dụng trong mã của mình, câu trả lời của @Jahnold thực sự tốt. Tuy nhiên, cần phải chỉ ra rằng MVP là một khái niệm mức cao, và có thể có nhiều triển khai theo nguyên tắc MVP - có nhiều cách để làm da mèo.

There is another implementation of MVP, dựa trên ý tưởng rằng Activities in Android are not UI Elements, chỉ định ActivityFragment làm diễn giả MVP. Trong cấu hình này, các diễn giả MVP có thể truy cập trực tiếp đến Context.

Bằng cách này, ngay cả trong việc thực hiện nói trên của MVP, tôi sẽ không sử dụng Context để có được quyền truy cập vào SharedPreferences trong người dẫn chương trình - Tôi vẫn sẽ định nghĩa một lớp wrapper cho SharedPreferences và tiêm nó vào người dẫn chương trình.

1

Hầu hết các phần tử tên miền, như DB hoặc mạng, cần bối cảnh được xây dựng. Không thể tạo thay thế trong Chế độ xem vì Chế độ xem không thể có bất kỳ kiến ​​thức nào về Mô hình. Sau đó, chúng phải được tạo trong Presenter. Chúng có thể được tiêm bởi Dagger, nhưng nó cũng sử dụng Context.Vì vậy, bối cảnh được sử dụng trong Presenter xP

Hack là nếu chúng ta muốn tránh Context trong Presenter thì chúng ta chỉ có thể làm cho constructor tạo tất cả các đối tượng Model từ Context và không lưu nó. Nhưng theo ý kiến ​​của tôi, nó là ngu ngốc. JUnit mới trong Android có quyền truy cập vào Ngữ cảnh.

Một hack khác là làm cho Context vô hiệu hóa, và trong các đối tượng miền cần có cơ chế để cung cấp cá thể kiểm thử trong trường hợp null trong ngữ cảnh. Tôi cũng không thích hack này.

Các vấn đề liên quan