簡體   English   中英

Android MVP - 應避免在演示者中使用R.string引用嗎?

[英]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以避免混淆(例如ProductManagerMediaManager ......),因此演示者類僅用於保持工作流程。

經驗法則是沒有或至少限制在演示者類中導入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));
}

您正在使用viewcontext - 它是ActivityFragment

現在如果你被告知要將副本內容從R.string.res1R.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM