为了完全将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文件。这太难问了吗?
答案 0 :(得分:20)
我认为没有理由在 Presenter 中调用任何Android代码(但你总是可以这样做)。
所以在你的情况下:
查看/活动onCreate()调用 - > presenter.onCreate();
Presenter onCreate()调用 - > view.setTextLabel()或视图中的任何内容。
始终将Android SDK与演示者分离。
在Github中,你可以找到一些关于 MVP :
的例子答案 1 :(得分:6)
最好不要在演示者中使用依赖于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 architecture的表示层中,因此你可以使用像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。
答案 2 :(得分:1)
这将是一篇很长的文章,关于如何在我的答案的最后部分解决你的问题之前构建MVP项目。
我只是在自己的答案中报告MVP结构how to structure MVP project。
我经常将业务逻辑代码放在模型层中(不要与数据库中的模型混淆)。我经常重命名为XManager
以避免混淆(例如ProductManager
,MediaManager
...),因此演示者类仅用于保持工作流程。
经验法则规则中没有或至少限制导入android包。这个最佳实践支持您更轻松地测试presenter类,因为presenter现在只是一个简单的java类,所以我们不需要android框架来测试这些东西。
例如,这是我的mvp工作流程。
查看类:这是一个存储所有视图的地方,例如button,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();
}
}
经理类:这是核心业务逻辑代码。也许一位主持人会有很多经理(取决于视图的复杂程度)。通常我们通过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以及如何解决你的混凝土问题。希望这有帮助:)
答案 3 :(得分:1)
您的presenter
应该不需要了解如何显示展示用户界面的详细信息,以及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的完整文章,以防万一。