简体   繁体   English

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

[英]Android MVP - Should avoid using R.string references in presenter?

In an attempt to entirely decouple the Android SDK from my presenter classes, I'm trying to figure out the best way to avoid accessing resource IDs which we normally use R for. 为了完全将Android SDK与我的演示者类分离,我试图找出避免访问我们通常使用R的资源ID的最佳方法。 I thought I could just create an interface to access things like string resources, but I still need IDs to reference the strings. 我以为我可以创建一个接口来访问字符串资源之类的东西,但我仍然需要ID来引用字符串。 If I were to do something like... 如果我要做的事......

public class Presenter {
    private MyView view = ...;
    private MyResources resources = ...;

    public void initializeView() {
        view.setLabel(resources.getString(LABEL_RES_ID);
    }
}

I still have to have LABEL_RES_ID and then map it to R.string.label in my resources bridge. 我仍然需要LABEL_RES_ID ,然后将其映射到我的资源桥中的R.string.label It's cool because I could swap it out when unit testing with something else, but I don't want to manage another mapping to the string value. 这很酷,因为我可以在用其他东西进行单元测试时将其换掉,但我不想管理另一个到字符串值的映射。

If I give up and just use the R.string values, my presenter is bound to my view again. 如果我放弃并只使用R.string值,我的演示者将再次绑定到我的视图。 That's not ideal? 那不理想? Is there an easier solution that people use to get around this in order to keep them out of the presenter. 是否有一个更容易的解决方案,人们用来解决这个问题,以使他们远离主持人。 I don't want to manage strings in a way outside of what Android provides, because I still want to throw them in layout files and get the benefit of internationalization, etc. I want to make a dumb unit test that can work with this presenter without having to have the Android SDK generate the R.java files. 我不想以超出Android提供的方式管理字符串,因为我仍然希望将它们放在布局文件中并获得国际化等的好处。我想做一个可以与这个演示者一起工作的哑单元测试无需Android SDK即可生成R.java文件。 Is this too much to ask? 这要问太多了吗?

I consider that there's no reason to call any android code in Presenter (But you always can do it). 我认为没有理由在Presenter中调用任何Android代码(但你总是可以这样做)。

So in your case: 所以在你的情况下:

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

Presenter onCreate() calls -> view.setTextLabel() or whatever you want in the view. Presenter onCreate()调用 - > view.setTextLabel()或视图中的任何内容。

Always decouple Android SDK from presenters. 始终将Android SDK与演示者分离。

In Github, you can find some examples about MVP : 在Github中,您可以找到一些关于MVP的示例:

it's better to not use context and all object that depends on android sdk in presenter. 最好不要在演示者中使用依赖于android sdk的上下文和所有对象。 I send id of the String and view cast it into string. 我发送String的id并将视图转换为字符串。 like this-> 喜欢这个 - >

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

and get this on view like this 并在这样的视图上得到这个

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

With this approach you can test your method in presenter. 使用此方法,您可以在演示者中测试您的方法。 It depends on R object but it's okay. 它取决于R对象,但没关系。 all MVP classes placed in presentation layer in uncle bob clean architecture so you can use android objects like R class. 所有MVP类放在uncle bob clean架构的表示层中,这样你就可以使用像R类这样的android对象。 but in domain layer you have to use only regular java objects 但在域层中,您只能使用常规的Java对象

Update 更新

For those who want to reuse their code in other platforms you can use a wrapper class for mapping the id or enum types to the resources and get the string. 对于那些想要在其他平台上重用其代码的人,可以使用包装类将id或enum类型映射到资源并获取字符串。

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

The string resolver method is like this and the class can provided by View and DI into presenters. 字符串解析器方法是这样的,类可以由View和DI提供给演示者。

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

But I do not recommend this in most of cases because of over engineering! 但是在大多数情况下我不建议这样做,因为过度工程! you never need exact presentation code in other platforms in most of the times so: Better solution would be something like mocking that R class in other platforms because R class is already like a wrapper. 在大多数情况下,你从不需要在其他平台上使用精确的表示代码,因此:更好的解决方案就是在其他平台上模拟R类,因为R类已经像包装器一样。 You Should write your own R in other platform. 你应该在其他平台上编写自己的R.

This will be a long post about how to structure MVP project before getting into solving your problem at very last of my answer. 这将是一篇很长的文章,关于如何构建MVP项目,然后在我的答案的最后解决你的问题。

I just report MVP structure here how to structure MVP project from my own answer. 我只是在这里报告MVP结构如何从我自己的答案构建MVP项目

I often put business logic code in Model Layer (don't make confusion with model in database). 我经常将业务逻辑代码放在模型层中 (不要与数据库中的模型混淆)。 I often rename as XManager for avoiding confusion (such as ProductManager , MediaManager ...) so presenter class just uses for keeping workflow. 我经常将其重命名为XManager以避免混淆(例如ProductManagerMediaManager ......),因此演示者类仅用于保持工作流程。

The rule of thumb is no or at least limit import android package in presenter class. 经验法则是没有或至少限制在演示者类中导入android包 This best practice supports you easier in testing presenter class because presenter now is just a plain java class, so we don't need android framework for testing those things. 这个最佳实践支持您更轻松地测试presenter类,因为presenter现在只是一个普通的java类,所以我们不需要android框架来测试这些东西。

For example here is my mvp workflow. 例如,这是我的mvp工作流程。

View class : This is a place you store all your view such as button, textview ... and you set all listeners for those view components on this layer. 视图类 :这是一个存储所有视图的位置,例如按钮,textview ...并且您在此图层上设置这些视图组件的所有侦听器。 Also on this View, you define a Listener class for presenter implements later. 此视图上,您​​还可以在以后为演示者实现定义一个Listener类。 Your view components will call methods on this listener 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 class: This is where you store view and model inside for calling later. Presenter类:这是您在里面存储视图和模型以便稍后调用的地方。 Also presenter class will implement ViewListener interface has defined above. 另外,presenter类将实现上面定义的ViewListener接口。 Main point of presenter is control logic workflow. 演示者的要点是控制逻辑工作流程。

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 class: Here is the core business logic code. Manager类:这是核心业务逻辑代码。 Maybe one presenter will have many managers (depend on how complicate the view is). 也许一位主持人会有很多经理(取决于视图的复杂程度)。 Often we get Context class through some injection framework such as Dagger . 通常我们通过一些注入框架(如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();
   }
}

Finally: Put those thing together in Activities, Fragments ... Here is the place you initialize view, manager and assign all to presenter. 最后:将这些内容放在“活动”,“片段”中......这是您初始化视图,管理器并将所有内容分配给演示者的位置。

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

You see that each presenter, model, view is wrapped by one interface. 您会看到每个演示者,模型,视图都由一个界面包装。 Those components will called through interface. 这些组件将通过接口调用。 This design will make your code more robust and easier for modifying later. 此设计将使您的代码更加健壮,以后更容易修改。

In short, in your situation, I propose this design: 总之,在你的情况下,我提出这个设计:

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

In case the logic view is complicated, for example some conditions for setting value. 在逻辑视图复杂的情况下,例如用于设置值的一些条件。 So I will put logic into DataManager for getting back text. 所以我将把逻辑放到DataManager以获取文本。 For example: 例如:

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

So you never put android related-thing into presenter class. 所以你永远不会把android相关的东西放到演示者类中。 You should move that to View class or DataManager class depend on context. 您应该根据上下文将其移动到View类或DataManager类。

This is a very long post discuss in detail about MVP and how to solve your concreted problem. 这是一个非常长的帖子,详细讨论了MVP以及如何解决你的混凝土问题。 Hope this help :) 希望这有帮助:)

Your presenter should NOT need to know about how to show the details of showing the UI, and as such the R.string references. 您的presenter应该知道如何显示显示UI的详细信息,因此R.string引用。

Let's suppose you encounter a network issue and you want to show the user a network error message. 假设您遇到网络问题,并且您希望向用户显示网络错误消息。

The first (wrong IMO) thing would be to get the context from the view and call some method like this in your presenter : 第一个(错误的IMO)事情是从view获取上下文并在presenter调用这样的方法:

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

In which you're using the context from your view -- which is either an Activity or a Fragment . 您正在使用viewcontext - 它是ActivityFragment

Now what if you're told to change the copy content from R.string.res1 to R.string.res2 ? 现在如果你被告知要将副本内容从R.string.res1R.string.res2怎么R.string.res2 which component should you change? 你应该改变哪个组件?

The view . view But is that necessary? 但那有必要吗?

I believe not, because what is important for the presenter is that the view shows a message regarding network error, be it "Network error! please try again" or "There is a network error. Please try later." 我不相信,因为对于presenter来说重要的是view显示有关网络错误的消息,无论是“网络错误!请再试一次”或“网络错误。请稍后再试”。

So what is the better way? 那么更好的方法是什么?

Change your presenter to the following: presenter更改为以下内容:

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

and leave the implementation details to the view : 并将实现细节留给view

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

I have written a complete article on MVP here , just in case. 我在这里写了一篇关于MVP的完整文章,以防万一。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM