36

为了将 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 文件。这要求太多了吗?

4

4 回答 4

22

我认为没有理由在Presenter中调用任何 android 代码(但你总是可以这样做)。

所以在你的情况下:

查看/活动 onCreate() 调用 -> presenter.onCreate();

演示者 onCreate() 调用 -> view.setTextLabel() 或视图中您想要的任何内容。

始终将 Android SDK 与演示者分离。

在 Github,你可以找到一些关于MVP的例子:

于 2014-11-21T21:07:55.763 回答
6

最好不要在presenter中使用上下文和依赖于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
}

使用这种方法,您可以在 Presenter 中测试您的方法。它取决于 R 对象,但没关系。所有 MVP 类都放置在uncle bob clean 架构中的表示层中,因此您可以使用像 R 类这样的 android 对象。但是在域层中,您必须只使用常规的 java 对象

更新

对于那些想在其他平台上重用代码的人,您可以使用包装类将 id 或 enum 类型映射到资源并获取字符串。

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

字符串解析器方法是这样的,类可以由 View 和 DI 提供给 Presenters。

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

但在大多数情况下,我不建议这样做,因为过度工程!在大多数情况下,您永远不需要在其他平台上使用精确的演示代码,因此:更好的解决方案是在其他平台上模拟该 R 类,因为 R 类已经像一个包装器。您应该在其他平台上编写自己的 R。

于 2016-08-22T05:29:32.317 回答
3

presenter不需要知道如何显示显示UI的细节以及R.string参考。

假设您遇到网络问题,并且想要向用户显示网络错误消息。

第一件(错误的 IMO)事情是从 获取上下文view并在您的 中调用类似这样的方法presenter

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

您在其中使用context来自您的view-- 是 anActivity或 a Fragment

现在,如果您被告知将复制内容从 更改R.string.res1R.string.res2怎么办?您应该更改哪个组件?

view。_ 但这有必要吗?

我不相信,因为重要的presenterview显示有关网络错误的消息,无论是“网络错误!请重试”或“网络错误。请稍后再试”。

那么更好的方法是什么?

将您更改presenter为以下内容:

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

并将实施细节留给view

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

我在这里写了一篇关于 MVP 的完整文章,以防万一。

于 2018-04-08T01:03:52.957 回答
1

这将是一篇关于如何在我最后回答您的问题之前构建 MVP 项目的长篇文章。

我只是在这里报告 MVP 结构如何根据我自己的答案构建 MVP 项目。

我经常将业务逻辑代码放在模型层中(不要与数据库中的模型混淆)。我经常重命名XManager以避免混淆(例如ProductManagerMediaManager...),因此演示者类仅用于保持工作流程。

经验法则是没有或至少限制在演示者类中导入 android 包。这个最佳实践支持您更轻松地测试演示者类,因为演示者现在只是一个普通的 java 类,所以我们不需要 android 框架来测试这些东西。

例如,这是我的 mvp 工作流程。

视图类:这是您存储所有视图(例如按钮、文本视图...)的地方,您可以在该层上为这些视图组件设置所有侦听器。同样在此视图上,您​​稍后为演示者实现定义一个侦听器类。您的视图组件将调用此侦听器类的方法。

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类:这里是核心业务逻辑代码。也许一个演示者会有很多经理(取决于视图的复杂程度)。通常我们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();
   }
}

您会看到每个演示者、模型、视图都由一个接口包装。这些组件将通过接口调用。这种设计将使您的代码更健壮,并且更易于以后修改。

简而言之,在您的情况下,我提出这样的设计:

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 以及如何解决您的具体问题。希望这有帮助:)

于 2016-11-27T15:25:36.513 回答