web-dev-qa-db-ja.com

Android MVP-プレゼンターでR.string参照の使用を避けるべきですか?

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ファイルを生成しなくても、このプレゼンターと連携できるダムユニットテスト。これは多すぎて質問できませんか?

33
Scott Merritt

Androidコード内のPresenterを呼び出す理由はないと私は考えています(ただし、いつでもそれを実行できます)。

だからあなたの場合:

ビュー/アクティビティonCreate()コール-> presenter.onCreate();

Presenter onCreate()呼び出し-> view.setTextLabel()またはビューで必要なものを呼び出します。

常にAndroid SDKを発表者から切り離します。

Githubには、[〜#〜] mvp [〜#〜]に関する例がいくつかあります。

21
PaNaVTEC

プレゼンターでAndroid SDKに依存するコンテキストとすべてのオブジェクトを使用しない方が良いでしょう。文字列のIDを送信し、それを文字列にキャストします。このように->

getview().setTitle(R.string.hello);

これをこのように表示します

@Override
public void setTitle(int id){
String text=context.getString(id);
//do what you want to do
}

このアプローチを使用すると、プレゼンターでメソッドをテストできます。 Rオブジェクトにもよりますが大丈夫です。 ncle bob clean architecture のプレゼンテーション層に配置されたすべてのMVPクラス。RクラスのようなAndroidオブジェクトを使用できます。ドメインレイヤーでは、通常のJavaオブジェクトのみを使用する必要があります

更新

他のプラットフォームでコードを再利用したい場合は、ラッパークラスを使用してIDまたは列挙型をリソースにマッピングし、文字列を取得できます。

getView().setTitle(myStringTools.resolve(HELLO));

文字列リゾルバーメソッドはこのようなものであり、クラスはViewおよびDIによってプレゼンターに提供できます。

Public String resolve(int ourID){
return context.getString(resourceMap.getValue(ourID));
}

しかし、過剰なエンジニアリングのため、ほとんどの場合これはお勧めしません!ほとんどの場合、他のプラットフォームで正確なプレゼンテーションコードが必要になることはありません。Rクラスはすでにラッパーのようなものであるため、他のプラットフォームでそのRクラスをモックするような解決策が適しています。他のプラットフォームで独自のRを作成する必要があります。

6
Siavash Abdoli

presenterは、UIの表示の詳細を表示する方法を知っている必要があります[〜#〜] [〜#〜]。 、およびそのようなR.string参照。

ネットワークの問題が発生し、ユーザーにネットワークエラーメッセージを表示するとします。

最初の(間違ったIMO)ことは、viewからコンテキストを取得し、presenterで次のようなメソッドを呼び出すことです。

public void showNetworkError(){
    presenter.showMessage(view.getResources().getString(R.string.res1));
}

contextからviewを使用している場合、これはActivityまたはFragmentのいずれかです。

次に、コピーの内容をR.string.res1からR.string.res2に変更するように指示された場合はどうなりますか?どのコンポーネントを変更する必要がありますか?

view。しかし、それは必要ですか?

presenterにとって重要なのは、viewが「ネットワークエラー!もう一度やり直してください」または「ネットワークエラーが発生しています。後でお試しください。"

それで、より良い方法は何ですか?

presenterを次のように変更します。

public void showNetworkError(){
    view.showNetworkErrorMessage();
}

実装の詳細はviewにお任せください。

public void showNetworkErrorMessage(){
    textView.setText(R.string.resX)
}

念のため、MVP here に関する完全な記事を書きました。

1
Ali Nem

これは、私の回答の最後に問題を解決する前にMVPプロ​​ジェクトを構成する方法についての長い記事になります。

私はここでMVP構造を報告するだけです MVPプロ​​ジェクトを構造化する方法 私の答えから。

私はビジネスロジックコードをModel Layerに配置することがよくあります(データベースのモデルと混同しないでください)。私はしばしば混乱を避けるためにXManagerに名前を変更します(ProductManagerMediaManager ...など)。したがって、プレゼンタークラスはワークフローを維持するためだけに使用します。

経験則では、プレゼンタークラスではimport Android package)が制限されていません。このベストプラクティスは、プレゼンタークラスのテストでは、プレゼンターは単純なJavaクラスなので、これらをテストするためのAndroidフレームワークは必要ありません。

たとえば、これは私のmvpワークフローです。

View class:これは、ボタン、テキストビューなどのすべてのビューを保存し、このレイヤーのビューコンポーネントのすべてのリスナーを設定する場所です。また、このビューでは、プレゼンター実装用のリスナークラスを後で定義します。ビューコンポーネントは、このリスナークラスのメソッドを呼び出します。

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クラス:これは、後で呼び出すためにビューとモデルを格納する場所です。また、プレゼンタークラスは、上記で定義した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クラス:これがコアビジネスロジックコードです。たぶん、1人のプレゼンターに多くのマネージャーがいます(ビューがどれほど複雑かによって異なります)。多くの場合、Contextなどのインジェクションフレームワークを通じてDaggerクラスを取得します。

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();
   }
}

各プレゼンター、モデル、ビューが1つのインターフェースでラップされていることがわかります。これらのコンポーネントはインターフェースを介して呼び出されます。この設計により、コードがより堅牢になり、後で変更しやすくなります。

要するに、あなたの状況で、私はこのデザインを提案します:

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 related-thingをプレゼンタークラスに入れないでください。コンテキストに応じて、ViewクラスまたはDataManagerクラスに移動する必要があります。

これは、MVPと具体的な問題を解決する方法について詳しく説明する非常に長い投稿です。この助けを願っています:)

1
hqt