Pull to refresh
66.15
red_mad_robot
№1 в разработке цифровых решений для бизнеса

Сажаем контроллеры на диету: Android

Reading time 5 min
Views 29K
Паттерн MVС появился достаточно давно и создавался с целью разделения бизнес-логики приложения от представления. Но далеко не все программисты реализуют его правильно, из-за чего возникают «Толстые тупые уродливые контроллеры» содержащие тонны кода. В этой статье пойдет речь о правильной реализации View классов, для того чтобы уменьшить количество кода в контроллерах и оставить место чистой бизнес-логике приложения.



Все, наверное, должны знать что MVC бывает двух типов — с активной моделью и пассивной, различие которых кроется в том, что пассивная модель служит простым источником данных (как, например, DAO для базы данных), а активная модель сама обновляет состояние своих подписчиков — View. Пассивная модель является более универсальной и простой, кроме того чаще всего используется в разработке, поэтому она будет использоваться для примера в этой статье. Давайте взглянем на её схему.

Пользователь взаимодействует с контроллером, контроллер запрашивает данные у модели и заполняет View, который отображается пользователю, всё просто.

  • При использовании MVC в Android, Activity или Fragment является контроллером.
  • Модель — набор классов, которые служат источником данных приложения.
  • View — xml разметка и кастомные View компоненты, на подобие Button и т. д.

Если с контроллером и моделью, вроде бы, всё понятно, то со View возникают некоторые трудности, главная их причина — View, как такого, нет, никто не задумывается о создании отдельных View классов с интерфейсом, через который контроллер мог бы передавать данные для отображения. Большинство просто создаёт xml разметку и заполняет её прямо в контроллере, из-за чего код, который по идее должен содержать бизнес-логику переполняется деталями отображения, такими как цвет текста, размер шрифта, установка текста в TextView, работа с ActionBar'ом, NavigatonDrawer'ом и прочими. В результате код Activity разрастается до 1000 строк и на первый взгляд содержит какой-то мусор.

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

Наше приложение будет решать вполне распространенную задачу — загружать и отображать профайл пользователя. Начнем реализацию.

Для этого создадим модельный класс User, в котором будет храниться имя и фамилия пользователя.

public class User {

    private final String firstname;
    private final String lastname;

    public User(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
    }

    // getters
}

И класс provider, который будет её «загружать». Этот класс создан для демонстрационных целей, в реальном проекте не следует использовать AsyncTask для загрузки данных и не стоит писать свой велосипед, который даже не учитывает жизненный цикл Activity и не обрабатывает ошибки, лучше использовать готовое решение, например, RoboSpice. Здесь этот класс нужен, по большей части, только для того, чтобы скрыть детали реализации загрузки данных в отдельном потоке.

public class UserProvider {

    // результат вернем в Callback
    public void loadUser(Callback callback) {
        new LoadUserTask(callback).execute();
    }

    public class LoadUserTask extends AsyncTask<Void, Void, User> {
        private Callback callback;

        public LoadUserTask(Callback callback) {
            this.callback = callback;
        }

        @Override
        protected User doInBackground(Void... params) {
            User user = new User("firstname", "lastname");
            return user;
        }

        @Override
        protected void onPostExecute(User user) {
            super.onPostExecute(user);
            callback.onUserLoaded(user);
        }
    }

    public interface Callback {
        void onUserLoaded(User user);
    }
}

Далее создается xml верстка, которую мы опустим и контроллер, который должен связать View и Model, и внести немного бизнес-логики в наше приложение. В виде контроллера выступает Activity, обычно он реализуется примерно так:

public class UserProfileActivity extends Activity implements Callback {

    private TextView firstnameTxt, lastnameTxt;
    private ProgressBar progressBar;
    private UserProvider userProvider;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_profile);

        firstnameTxt = (TextView) findViewById(R.id.firstname);
        lastnameTxt = (TextView) findViewById(R.id.lastname);
        progressBar = (Progressbar) findViewById(R.id.progressBar);

        userProvider = new UserProvider();
        loadUser();
    }

    @Override
    public void onUserLoaded(User user) {
        hideProgressBar();
        showUser(user);
    }
    
    private void loadUser() {
        showProgressBar();
        userProvider.loadUser(this);
    }

    public void showUser(User user) {
        firstnameTxt.setText(user.getFirstname());
        lastnameTxt.setText(user.getLastname());
    }

    public void showProgressBar() {
        progressBar.setVisibility(View.VISIBLE);
    }

    public void hideProgressBar() {
        progressBar.setVisibility(View.INVISIBLE);
    }
}

При открытии экрана начинается загрузка профайла, отображается progress bar, когда профайл будет загружен, progress bar скрывается и происходит наполнение экрана данными.
Как видно из этого кода — в нём перемешивается работа с представлением и бизнес-логика.
Если сейчас все выглядит не так плохо, то при развитии проекта такой код станет плохочитаемым и трудноподдерживаемым.

Давайте вспомним про ООП и добавим немного абстракции в наш код.

public class UserView {

    private final TextView firstnameTxt, lastnameTxt;
    private final ProgressBar progressBar;

    public UserView(View rootView) {
        firstnameTxt = (TextView) rootView.findViewById(R.id.firstname);
        lastnameTxt = (TextView) rootView.findViewById(R.id.lastname);
        progressBar = (ProgressBar) rootView.findViewById(R.id.progressBar);
    }

    public void showUser(User user) {
        firstnameTxt.setText(user.getFirstname());
        lastnameTxt.setText(user.getLastname());
    }

    public void showProgressBar() {
        progressBar.setVisibility(View.VISIBLE);
    }

    public void hideProgressBar() {
        progressBar.setVisibility(View.INVISIBLE);
    }
}

View берет на себя всю работу с представлением Activity. Для отображения профайла пользователя нужно просто воспользоваться методом showUser(User) и передать ему модельный объект. В реальном проекте для View желательно создать базовый класс, в который можно перенести вспомогательные методы, такие как showProgressBar(), hideProgressBar(), и другие. В результате вся логика работы с представлением вынесена из Activity в отдельную сущность, что в разы уменьшает объемы кода контроллера и создаёт прозрачную абстракцию работы с View.

Activity же теперь ничего не знает о TextView и других контролах. Все взаимодействие с представлением происходит с помощью класса UserView и его интерфейса.

public class UserProfileActivity extends Activity {

    private UserView userView;
    private UserProvider userProvider;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);

        userView = new UserView(getWindow().getDecorView())
        userProvider = new UserProvider();

        loadUser();
    }

    @Override
    public void onUserLoaded(User user) {
        userView.hideProgressBar();
        userView.showUser(user);
    }

    private void loadUser() {
        userView.showProgressBar();
        userProvider.loadUser(this);
    }
}

Теперь контроллер оперирует всего двумя сущностями — UserView и UserProvider, в нём нет тонкостей реализации отображения данных. Код стал чище и понятней.

Сейчас класс UserView просто отображает данные, возможно вы захотите сделать сохранение состояния между поворотами экранов — этот вопрос можно легко решить создав метод, записывающий состояние View в Parcelable или Bundle. Также, скорей всего, понадобится возможность обработки нажатий, в этом случае сам OnClickListener лучше создать во View классе и в него передать Callback, который реализует ваш контроллер.

Вот, собственно, и все. Так решается проблема недооценённых View в Android. При использовании этого подхода количество кода в ваших контроллерах заметно уменьшится, уровень абстракций возрастет и доллар опять будет стоить 30 рублей.

Читайте также:
Стилизация iOS-приложений: как мы натягиваем шрифты, цвета и изображения
Архитектурный дизайн мобильных приложений
Архитектурный дизайн мобильных приложений: часть 2
Tags:
Hubs:
+14
Comments 52
Comments Comments 52

Articles

Information

Website
redmadrobot.ru
Registered
Founded
Employees
1,001–5,000 employees
Location
Россия