Android MVP - Следует избегать использования ссылок R.string в презентаторе?
В попытке полностью отделить Android SDK от моих классов презентаторов, я пытаюсь найти лучший способ избежать доступа к идентификаторам ресурсов, которые мы обычно используем для R. Я думал, что могу просто создать интерфейс для доступа к таким вещам, как строковые ресурсы, но мне все еще нужны идентификаторы для ссылки на строки. Если бы я сделал что-то вроде...
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, потому что я все еще хочу бросить их в файлы макета и получить выгоду от интернационализации и т.д. Я хочу сделать немой unit test, который может работать с этим презентатор, не требуя, чтобы Android SDK генерировал файлы R.java. Это слишком много, чтобы спросить?
Ответы
Ответ 1
Я считаю, что нет причин для вызова любого кода Android в Presenter (но вы всегда можете это сделать).
Итак, в вашем случае:
Просмотр/действие onCreate() calls → presenter.onCreate();
Вызов Presenter onCreate() → view.setTextLabel() или все, что вы хотите в представлении.
Всегда отключайте Android SDK от презентаторов.
В Github вы можете найти несколько примеров MVP:
Ответ 2
лучше не использовать контекст и весь объект, который зависит от android sdk в презентаторе. Я отправляю идентификатор строки и просматриваю ее в строку. вроде этого →
getview().setTitle(R.string.hello);
и получим это как показано
@Override
public void setTitle(int id){
String text=context.getString(id);
//do what you want to do
}
При таком подходе вы можете протестировать свой метод в презентаторе. Это зависит от объекта R, но все в порядке. все классы MVP, размещенные в слое представления в чистая архитектура дяди bob, вы можете использовать объекты Android, такие как R-класс. но в доменном слое вы должны использовать только обычные объекты java
Обновление
Для тех, кто хочет повторно использовать свой код на других платформах, вы можете использовать класс-оболочку для сопоставления типов идентификаторов или перечислений с ресурсами и получения строки.
getView().setTitle(myStringTools.resolve(HELLO));
Метод распознавания строк подобен этому, и класс может быть представлен View и DI в презентаторах.
Public String resolve(int ourID){
return context.getString(resourceMap.getValue(ourID));
}
Но я не рекомендую это в большинстве случаев из-за чрезмерной инженерии! вам никогда не нужен точный код представления на других платформах в большинстве случаев, поэтому:
Лучшее решение было бы чем-то вроде насмешки над этим классом R на других платформах, потому что R-класс уже похож на обертку. Вы должны написать свой собственный R на другой платформе.
Ответ 3
Это будет длинный пост о том, как структурировать проект MVP, прежде чем приступать к решению вашей проблемы в самом последнем ответе.
Я просто сообщаю структуру MVP здесь как структурировать проект MVP из моего собственного ответа.
Я часто ставил код бизнес-логики в Model Layer (не путайтесь с моделью в базе данных). Я часто переименовываю как XManager
для избежания путаницы (например, ProductManager
, MediaManager
...), поэтому класс презентатора просто используется для поддержания рабочего процесса.
Эмпирическое правило не является или, по крайней мере, ограничивает пакет импорта android в классе презентатора. Эта лучшая практика помогает вам легче тестировать класс презентатора, потому что ведущий теперь просто простой 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();
}
}
Класс презентатора: Здесь вы храните представление и модель внутри для вызова позже. Также класс презентатора будет реализовывать интерфейс 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 и как решить вашу конкретную проблему. Надеюсь, что эта помощь:)