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, потому что я все еще хочу добавлять их в файлы макета и получать выгоду от интернационализации и т. д. Я хочу сделать тупой модульный тест, который может работать с этим презентатором. без необходимости использования Android SDK для создания файлов R.java. Это слишком много, чтобы спросить?


person Scott Merritt    schedule 11.09.2014    source источник
comment
Чем больше я использую MVP с Android, тем чаще задаюсь этими вопросами. Ресурсы и объект Context действительно трудно искоренить. Вы нашли удовлетворительное решение?   -  person loeschg    schedule 21.11.2014
comment
взгляните на эту статью и пример проекта, который может помочь: medium.com/@m_mirhoseini/   -  person Mohsen Mirhoseini    schedule 30.11.2016


Ответы (4)


Я считаю, что нет причин вызывать какой-либо код Android в Presenter (но вы всегда можете это сделать).

Итак, в вашем случае:

Просмотр/активность вызовов onCreate() -> Presenter.onCreate();

Presenter onCreate() вызывает -> view.setTextLabel() или что угодно в представлении.

Всегда отделяйте Android SDK от докладчиков.

На Github вы можете найти несколько примеров о MVP:

person PaNaVTEC    schedule 21.11.2014
comment
Не могли бы вы указать аргумент, который идет в view.setTextLabel()? Потому что, если вы это сделаете, это будет ответом на вопрос. В общем, представьте, если у вас есть логика в вашем докладчике для отображения сообщения R.string.a или R.string.b. Как бы вы передали эту строку для отображения в представлении? - person Joao Sousa; 04.06.2015
comment
Ваше представление должно быть пассивным, а ведущий — активным, поэтому ведущий знает, что представлять, а представление знает, как это представить. если вы хотите показать ошибку в своем представлении от вашего докладчика, вам нужно сделать вызов, например: view.showContactLoadingError(); и ваше представление может настроить сообщение в зависимости от контекста представления. Я обновил образцы с помощью другого репозитория, который я сделал. - person PaNaVTEC; 05.06.2015
comment
Спасибо @PaNaVTEC, это именно тот ответ, который я искал, и он имеет смысл. - person Joao Sousa; 05.06.2015
comment
если вы хотите зарегистрировать ошибку и использовать ресурс для получения строки ошибки, то? - person baybora.oren; 30.03.2016
comment
В зависимости от представления вы, возможно, будете отображать разные сообщения об ошибках, это то же самое, чтобы показать ошибку. Так что можно рассматривать ответственность. Если это не ваш случай, вы можете обернуть R.string и отправить соавтора своему докладчику. - person PaNaVTEC; 01.04.2016
comment
Что, если мой Presenter предназначен для динамического создания всплывающего окна маркера карты с очень простым HTML, содержащим некоторые локализованные строки. Как я могу сгенерировать его без доступа к ресурсам или как дать доступ к строкам в чистом виде? - person WindRider; 27.05.2016
comment
Я думаю использовать специальный класс форматирования, который технически находится на стороне просмотра. Докладчик делегирует ему большую часть работы по отображению фрагмента HTML. У него есть доступ к контексту. С DI это может быть даже чище. - person WindRider; 27.05.2016

лучше не использовать контекст и все объекты, зависящие от android sdk, в презентере. Я отправляю идентификатор строки и просматриваю ее в строку. вот так->

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, поэтому вы можете использовать объекты Android, такие как R класс. но на уровне домена вы должны использовать только обычные объекты Java

Обновить

Для тех, кто хочет повторно использовать свой код на других платформах, вы можете использовать класс-оболочку для сопоставления типов id или enum с ресурсами и получить строку.

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

Метод распознавателя строк подобен этому, и класс может предоставляться View и DI в презентаторах.

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

Но я не рекомендую это в большинстве случаев из-за чрезмерной инженерии! вам никогда не нужен точный код представления на других платформах в большинстве случаев, поэтому: лучшим решением будет что-то вроде насмешки над этим классом R на других платформах, потому что класс R уже похож на оболочку. Вы должны написать свой собственный R на другой платформе.

person Siavash Abdoli    schedule 22.08.2016
comment
Все еще не кажется правильным. Вы по-прежнему зависите от кода, созданного Android SDK. Я думаю, что у PaNaVTEC есть ответ выше, и хотя это кажется правильным способом сделать это, кажется, что требуется много работы, чтобы получить эти ссылки от докладчика. Если вы ссылаетесь на файл R, похоже, что ваш докладчик теперь зависит от функциональности Android (по крайней мере, код, который генерируется из Android SDK). - person Scott Merritt; 23.08.2016
comment
@zoonsf вы не можете принимать решения по логике в деятельности. Нет проблем с использованием ресурсов, и вы даже можете использовать ресурсы в модульном тесте. MVP находится на внешнем макете чистой архитектуры дяди Боба и зависит от Android, поэтому использование ресурсов в презентере является обычным и правильным. - person Siavash Abdoli; 23.08.2016

Ваш 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 здесь.

person Ali Nem    schedule 08.04.2018

Это будет длинный пост о том, как структурировать проект MVP, прежде чем приступить к решению вашей проблемы в самом конце моего ответа.

Я просто сообщаю о структуре MVP здесь как структурировать проект MVP из моего собственного ответа.

Я часто помещаю код бизнес-логики на уровень модели (не путайте с моделью в базе данных). Я часто переименовываю как XManager, чтобы избежать путаницы (например, ProductManager, MediaManager ...), поэтому класс докладчика просто используется для сохранения рабочего процесса.

Практическим правилом является отсутствие или, по крайней мере, ограничение импорта пакета Android в классе докладчика. Эта передовая практика помогает вам легче тестировать класс Presenter, потому что Presenter теперь является просто классом Java, поэтому нам не нужна среда Android для тестирования этих вещей.

Например, вот мой рабочий процесс mvp.

Класс просмотра: это место, где вы храните все свои представления, такие как кнопка, текстовое представление... и устанавливаете все слушатели для этих компонентов представления на этом уровне. Также в этом представлении вы позже определяете класс Listener для реализации Presenter. Компоненты вашего представления будут вызывать методы этого класса прослушивателя.

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 и о том, как решить вашу конкретную проблему. Надеюсь, это поможет :)

person hqt    schedule 27.11.2016
comment
Помимо того факта, что у ведущего не должно быть обратных вызовов Android (странно проверять это, и он перемещает спагетти Android на презентаторов), мне нравится ваше решение. Хотя, что делать, если в этих значениях нет никакой логики и их много? Использовать R.string.* в Manager или Presenter? Я бы предпочел оставить их в Presenter, чтобы избежать ненужного уровня абстракции AKA кода. Но, с другой стороны, они генерируются Android... Принципы написания кода противоречат друг другу. - person Przemo; 09.10.2017