繁体   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