[英]Android MVP - Should avoid using R.string references in presenter?
為了完全將Android SDK與我的演示者類分離,我試圖找出避免訪問我們通常使用R的資源ID的最佳方法。 我以為我可以創建一個接口來訪問字符串資源之類的東西,但我仍然需要ID來引用字符串。 如果我要做的事......
public class Presenter {
private MyView view = ...;
private MyResources resources = ...;
public void initializeView() {
view.setLabel(resources.getString(LABEL_RES_ID);
}
}
我仍然需要LABEL_RES_ID
,然后將其映射到我的資源橋中的R.string.label
。 這很酷,因為我可以在用其他東西進行單元測試時將其換掉,但我不想管理另一個到字符串值的映射。
如果我放棄並只使用R.string值,我的演示者將再次綁定到我的視圖。 那不理想? 是否有一個更容易的解決方案,人們用來解決這個問題,以使他們遠離主持人。 我不想以超出Android提供的方式管理字符串,因為我仍然希望將它們放在布局文件中並獲得國際化等的好處。我想做一個可以與這個演示者一起工作的啞單元測試無需Android SDK即可生成R.java文件。 這要問太多了嗎?
我認為沒有理由在Presenter中調用任何Android代碼(但你總是可以這樣做)。
所以在你的情況下:
查看/活動onCreate()調用 - > presenter.onCreate();
Presenter onCreate()調用 - > view.setTextLabel()或視圖中的任何內容。
始終將Android SDK與演示者分離。
在Github中,您可以找到一些關於MVP的示例:
最好不要在演示者中使用依賴於android sdk的上下文和所有對象。 我發送String的id並將視圖轉換為字符串。 喜歡這個 - >
getview().setTitle(R.string.hello);
並在這樣的視圖上得到這個
@Override
public void setTitle(int id){
String text=context.getString(id);
//do what you want to do
}
使用此方法,您可以在演示者中測試您的方法。 它取決於R對象,但沒關系。 所有MVP類放在uncle bob clean架構的表示層中,這樣你就可以使用像R類這樣的android對象。 但在域層中,您只能使用常規的Java對象
更新
對於那些想要在其他平台上重用其代碼的人,可以使用包裝類將id或enum類型映射到資源並獲取字符串。
getView().setTitle(myStringTools.resolve(HELLO));
字符串解析器方法是這樣的,類可以由View和DI提供給演示者。
Public String resolve(int ourID){
return context.getString(resourceMap.getValue(ourID));
}
但是在大多數情況下我不建議這樣做,因為過度工程! 在大多數情況下,你從不需要在其他平台上使用精確的表示代碼,因此:更好的解決方案就是在其他平台上模擬R類,因為R類已經像包裝器一樣。 你應該在其他平台上編寫自己的R.
這將是一篇很長的文章,關於如何構建MVP項目,然后在我的答案的最后解決你的問題。
我只是在這里報告MVP結構如何從我自己的答案構建MVP項目 。
我經常將業務邏輯代碼放在模型層中 (不要與數據庫中的模型混淆)。 我經常將其重命名為XManager
以避免混淆(例如ProductManager
, MediaManager
......),因此演示者類僅用於保持工作流程。
經驗法則是沒有或至少限制在演示者類中導入android包 。 這個最佳實踐支持您更輕松地測試presenter類,因為presenter現在只是一個普通的java類,所以我們不需要android框架來測試這些東西。
例如,這是我的mvp工作流程。
視圖類 :這是一個存儲所有視圖的位置,例如按鈕,textview ...並且您在此圖層上設置這些視圖組件的所有偵聽器。 此視圖上,您還可以在以后為演示者實現定義一個Listener類。 您的視圖組件將調用此偵聽器類上的方法。
class ViewImpl implements View {
Button playButton;
ViewListener listener;
public ViewImpl(ViewListener listener) {
// find all view
this.listener = listener;
playButton.setOnClickListener(new View.OnClickListener() {
listener.playSong();
});
}
public interface ViewListener {
playSong();
}
}
Presenter類:這是您在里面存儲視圖和模型以便稍后調用的地方。 另外,presenter類將實現上面定義的ViewListener接口。 演示者的要點是控制邏輯工作流程。
class PresenterImpl extends Presenter implements ViewListener {
private View view;
private MediaManager mediaManager;
public PresenterImpl(View, MediaManager manager) {
this.view = view;
this.manager = manager;
}
@Override
public void playSong() {
mediaManager.playMedia();
}
}
Manager類:這是核心業務邏輯代碼。 也許一位主持人會有很多經理(取決於視圖的復雜程度)。 通常我們通過一些注入框架(如Dagger
獲取Context
類。
Class MediaManagerImpl extends MediaManager {
// using Dagger for injection context if you want
@Inject
private Context context;
private MediaPlayer mediaPlayer;
// dagger solution
public MediaPlayerManagerImpl() {
this.mediaPlayer = new MediaPlayer(context);
}
// no dagger solution
public MediaPlayerManagerImpl(Context context) {
this.context = context;
this.mediaPlayer = new MediaPlayer(context);
}
public void playMedia() {
mediaPlayer.play();
}
public void stopMedia() {
mediaPlayer.stop();
}
}
最后:將這些內容放在“活動”,“片段”中......這是您初始化視圖,管理器並將所有內容分配給演示者的位置。
public class MyActivity extends Activity {
Presenter presenter;
@Override
public void onCreate() {
super.onCreate();
IView view = new ViewImpl();
MediaManager manager = new MediaManagerImpl(this.getApplicationContext());
// or this. if you use Dagger
MediaManager manager = new MediaManagerImpl();
presenter = new PresenterImpl(view, manager);
}
@Override
public void onStop() {
super.onStop();
presenter.onStop();
}
}
您會看到每個演示者,模型,視圖都由一個界面包裝。 這些組件將通過接口調用。 此設計將使您的代碼更加健壯,以后更容易修改。
總之,在你的情況下,我提出這個設計:
class ViewImpl implements View {
Button button;
TextView textView;
ViewListener listener;
public ViewImpl(ViewListener listener) {
// find all view
this.listener = listener;
button.setOnClickListener(new View.OnClickListener() {
textView.setText(resource_id);
});
}
}
在邏輯視圖復雜的情況下,例如用於設置值的一些條件。 所以我將把邏輯放到DataManager
以獲取文本。 例如:
class Presenter {
public void setText() {
view.setText(dataManager.getProductName());
}
}
class DataManager {
public String getProductName() {
if (some_internal_state == 1) return getResources().getString(R.string.value1);
if (some_internal_state == 2) return getResources().getString(R.string.value2);
}
}
所以你永遠不會把android相關的東西放到演示者類中。 您應該根據上下文將其移動到View
類或DataManager
類。
這是一個非常長的帖子,詳細討論了MVP以及如何解決你的混凝土問題。 希望這有幫助:)
您的presenter
者不應該知道如何顯示顯示UI的詳細信息,因此R.string
引用。
假設您遇到網絡問題,並且您希望向用戶顯示網絡錯誤消息。
第一個(錯誤的IMO)事情是從view
獲取上下文並在presenter
調用這樣的方法:
public void showNetworkError(){
presenter.showMessage(view.getResources().getString(R.string.res1));
}
您正在使用view
的context
- 它是Activity
或Fragment
。
現在如果你被告知要將副本內容從R.string.res1
為R.string.res2
怎么R.string.res2
? 你應該改變哪個組件?
view
。 但那有必要嗎?
我不相信,因為對於presenter
來說重要的是view
顯示有關網絡錯誤的消息,無論是“網絡錯誤!請再試一次”或“網絡錯誤。請稍后再試”。
那么更好的方法是什么?
將presenter
更改為以下內容:
public void showNetworkError(){
view.showNetworkErrorMessage();
}
並將實現細節留給view
:
public void showNetworkErrorMessage(){
textView.setText(R.string.resX)
}
我在這里寫了一篇關於MVP的完整文章,以防萬一。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.