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ファイルを生成しなくても、このプレゼンターと連携できるダムユニットテスト。これは多すぎて質問できませんか?
Androidコード内のPresenterを呼び出す理由はないと私は考えています(ただし、いつでもそれを実行できます)。
だからあなたの場合:
ビュー/アクティビティonCreate()コール-> presenter.onCreate();
Presenter onCreate()呼び出し-> view.setTextLabel()またはビューで必要なものを呼び出します。
常にAndroid SDKを発表者から切り離します。
Githubには、[〜#〜] mvp [〜#〜]に関する例がいくつかあります。
プレゼンターで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を作成する必要があります。
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 に関する完全な記事を書きました。
これは、私の回答の最後に問題を解決する前にMVPプロジェクトを構成する方法についての長い記事になります。
私はここでMVP構造を報告するだけです MVPプロジェクトを構造化する方法 私の答えから。
私はビジネスロジックコードをModel Layerに配置することがよくあります(データベースのモデルと混同しないでください)。私はしばしば混乱を避けるためにXManager
に名前を変更します(ProductManager
、MediaManager
...など)。したがって、プレゼンタークラスはワークフローを維持するためだけに使用します。
経験則では、プレゼンタークラスでは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と具体的な問題を解決する方法について詳しく説明する非常に長い投稿です。この助けを願っています:)