Урок 9. Создание POJO объекта User. Работа с View из java кода

Код начала урока:

gitzip

Структура урока:

Видео версия урока

Создание View объектов в java коде. Связывание их с xml объектами

Первое, что нам надо сделать сегодня – это создать все наши View-компоненты в java коде и связать их с элементами в layout файле. Для этого в Activity нам необходимо для каждого View-компонента вызвать метод findViewById(int id) (рус. найти элемент по идентификатору), на вход которому необходимо передать идентификатор (id) элемента, который прописан нами в xml-файле. Выглядит это так:

UserInfoActivity.java

public class UserInfoActivity extends AppCompatActivity {
    private ImageView userImageView;
    private TextView nameTextView;
    private TextView  nickTextView;
    private TextView  descriptionTextView;
    private TextView  locationTextView;
    private TextView followingCountTextView;
    private TextView followersCountTextView;

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

        userImageView = findViewById(R.id.user_image_view);
        nameTextView = findViewById(R.id.user_name_text_view);
        nickTextView = findViewById(R.id.user_nick_text_view);
        descriptionTextView = findViewById(R.id.user_description_text_view);
        locationTextView = findViewById(R.id.user_location_text_view);
        followingCountTextView = findViewById(R.id.following_count_text_view);
        followersCountTextView = findViewById(R.id.followers_count_text_view);
    }
}

Зачем нам это необходимо? На этапе создания layout файла мы можем не обращаться к java коду вообще. Но в обычном приложении все данные поступают в layout динамически из java кода. Поэтому давайте создадим метод displayUserInfo() и там просто присвоим нашим View какое-то временное содержимое. Этот методм нам нужно будет вызывать всякий раз, когда мы захотим обновить информацию на нашем экране:

UserInfoActivity.java

package colibri.dev.com.colibritweet;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;
import android.widget.TextView;

import com.squareup.picasso.Picasso;

public class UserInfoActivity extends AppCompatActivity {
    private ImageView userImageView;
    private TextView nameTextView;
    private TextView nickTextView;
    private TextView descriptionTextView;
    private TextView locationTextView;
    private TextView followingCountTextView;
    private TextView followersCountTextView;

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

        userImageView = findViewById(R.id.user_image_view);
        nameTextView = findViewById(R.id.user_name_text_view);
        nickTextView = findViewById(R.id.user_nick_text_view);
        descriptionTextView = findViewById(R.id.user_description_text_view);
        locationTextView = findViewById(R.id.user_location_text_view);
        followingCountTextView = findViewById(R.id.following_count_text_view);
        followersCountTextView = findViewById(R.id.followers_count_text_view);

        displayUserInfo();
    }

    private void displayUserInfo() {
        Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(userImageView);
        nameTextView.setText("DevColibri");
        nickTextView.setText("@devcolibri");
        descriptionTextView.setText("Sample description");
        locationTextView.setText("USA");
        followingCountTextView.setText("42");
        followersCountTextView.setText("42");
    }
}

Мы используем обычный метод setText(), чтобы установить какой-то текст в наши TextView. Этот код уже больше похож на реальный, потому что именно так мы и будем устанавливать данные в наши View, когда получим ответ от сервера.

Добавление POJO объекта User.

Простые объекты в java называют POJO. POJO (англ. Plain Old Java Object) — «старый добрый Java-объект», простой Java-объект, не унаследованный от какого-то специфического объекта и не реализующий никаких служебных интерфейсов сверх тех, которые нужны для бизнес-модели.

Объекты нужны для того, чтобы хранить информацию в удобном для нас виде. Т.е. когда у себя в коде мы представляем данные в виде объектов реального мира. Есть пользователь User, у которого есть имя (поле name) и т.д.

Единственное, что нам осталось сделать – это добавить объект, который будет представлять все данные, которыми обладает пользователь. В нашем случае нам нужны следующие поля:

  • Идентификатор пользователя id
  • Ссылка на фотографию imageUrl
  • Имя пользователя name
  • Ник пользователя nick
  • Описание пользователя description
  • Местоположение пользователя location
  • Количество тех, кого читает пользователь followingCount
  • Количество тех, кто читает пользователя followersCount

Давайте создадим новый файл User.java. Только давайте перед этим создадим новый пакет, где будем хранить все сущности нашего приложения. Для этого нажмите правой кнопкой по пакету (папке) colibri.dev.com.colibritweet и выберите New -> Package:

PojoPackage.png

Назовём наш пакет pojo:

PojoName.png

Затем создадим новый класс. Для этого нажмите правой кнопкой по пакету pojo и выберете New -> JavaClass. Введите имя User:

UserClassPopup.png

UserClassName.png

Видим, что у нас создался класс и мы можем добавлять в него свой код:

UserClassCreated.png

Отлично, давайте добавим поля, которые мы перечислили:

User.java

public class User {
    private long id;
    private String imageUrl;
    private String name;
    private String nick;
    private String description;
    private String location;
    private int followingCount;
    private int followersCount;
}

По умолчанию в сущностях используем модификатор доступа private, для того, чтобы не нарушать инкапсуляцию класса, чтобы внешний код не имел прямого доступа к значению этих полей. Т.е. инкапсуляция – это просто приём сокрытия данных от внешнего вмешательства. Никто кроме класса User не получит доступ к его полям, если только в классе не будут объявлены соответствующие механизмы (например, конструктор, setter-методы, getter-методы).

Поэтому, чтобы установить значения полям, мы создадим конструктор.

Конструктор мы можем создавать либо вручную, либо использовать функции AndroidStudio, чтобы она сделала эту рутинную работу за нас. Для этого надо зажать клавиши Alt + Insert, выбрать из контекстного меню Constructor:

UserClassConstructorPopup.png

После этого необходимо выбрать поля, которые мы хотим получать в конструкторе. В нашем случае – это все поля, поэтому выбираем все:

UserClassConstructorFields.png

В появившемся окне нажимаем OK и видим, что конструктор сгенерировался автоматически.

User.java

public class User {
    private long id;
    private String imageUrl;
    private String name;
    private String nick;
    private String description;
    private String location;
    private int followingCount;
    private int followersCount;

    public User(long id,
                String imageUrl,
                String name,
                String nick,
                String description,
                String location,
                int followingCount,
                int followersCount) {
        this.id = id;
        this.imageUrl = imageUrl;
        this.name = name;
        this.nick = nick;
        this.description = description;
        this.location = location;
        this.followingCount = followingCount;
        this.followersCount = followersCount;
    }
}

Также (т.к. поля у нас private) нам необходимо создать getter методы для всех полей. Давайте тоже сделаем это автоматически.

По аналогии с генерацией конструктора:

  • зажимаем клавиши Alt + Insert,
  • выбираем из контекстного меню Getter.
  • выбираем все поля
  • нажимаем OK.

Наш класс теперь выглядит так:

User.java

public class User {
    private long id;
    private String imageUrl;
    private String name;
    private String nick;
    private String description;
    private String location;
    private int followingCount;
    private int followersCount;

    public User(long id,
                String imageUrl,
                String name,
                String nick,
                String description,
                String location,
                int followingCount,
                int followersCount) {
        this.id = id;
        this.imageUrl = imageUrl;
        this.name = name;
        this.nick = nick;
        this.description = description;
        this.location = location;
        this.followingCount = followingCount;
        this.followersCount = followersCount;
    }

    public long getId() {
        return id;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public String getName() {
        return name;
    }

    public String getNick() {
        return nick;
    }

    public String getDescription() {
        return description;
    }

    public String getLocation() {
        return location;
    }

    public int getFollowingCount() {
        return followingCount;
    }

    public int getFollowersCount() {
        return followersCount;
    }
}

Мы можем работать с таким классом, но для POJO классов есть ключевое правило: всегда переопределять методы equals() и hashcode(). Если вы не знаете, зачем это нужно, то прервитесь и изучите эту статью.

Эти методы мы тоже сгенерируем автоматически. Для этого снова зажимаем клавиши Alt + Insert, выбираем equals() and hashcode(). После этого перед вами появится окно такого вида:

UserClassEqualsHashCode.png

Здесь мы можем выбрать какой-то кастомный template для генерации методов, но пока будем пользоваться Intellij Default.

Нажимаем Next. После этого видим форму, в которой надо выбрать поля, которые будут включены в метод equals(). Выбираем все поля и нажимаем Next:

UserClassEqualsFields.png

После этого видим форму, в которой надо выбрать поля, которые будут включены в метод hashcode(). Снова выбираем все поля и нажимаем Next:

UserClassHashcodeFields.png

В следующей форме нужно отметить, какие поля у нас всегда будут not null. Это необходимо для того, чтобы во время выполнения методов equals() и hashcode() приложение не сломалось с null pointer exception. В нашем случае у пользователя обязательно должны быть поля id, name, nick. Т.к. поле id является примитивом (типом long), то оно не может быть null не при каких обстоятельствах. Поэтому мы выбираем поля name, nick:

UserClassEqualsHashcodeNonNull.png

Таким образом, у нас сгенерировались эти два метода:

User.java

public class User {

    // Остальной код выше не изменился

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (id != user.id) return false;
        if (followingCount != user.followingCount) return false;
        if (followersCount != user.followersCount) return false;
        if (imageUrl != null ? !imageUrl.equals(user.imageUrl) : user.imageUrl != null)
            return false;
        if (!name.equals(user.name)) return false;
        if (!nick.equals(user.nick)) return false;
        if (description != null ? !description.equals(user.description) : user.description != null) {
            return false;
        }
        return location != null ? location.equals(user.location) : user.location == null;
    }

    @Override
    public int hashCode() {
        int result = (int) (id ^ (id >>> 32));
        result = 31 * result + (imageUrl != null ? imageUrl.hashCode() : 0);
        result = 31 * result + name.hashCode();
        result = 31 * result + nick.hashCode();
        result = 31 * result + (description != null ? description.hashCode() : 0);
        result = 31 * result + (location != null ? location.hashCode() : 0);
        result = 31 * result + followingCount;
        result = 31 * result + followersCount;
        return result;
    }
}

Если вы не переопределите эти два методы, то объекты будут сравниваться по ссылке, иначе по тому алгоритму, который описан в этих методах.

Использование POJO User в UserInfoActivity

Теперь давайте добавим объект User в нашу Activity. Это необходимо, потому что от сервера мы получим именно такой объект. Т.е. на самом деле процесс получения данных пользователя делится на два действия:

  • Получение данных(объекта User) от источника данных (сервер, база данных, или заглушка для тестов).
  • Отображение данных.

Давайте создадим метод getUser(), в котором пока будем возвращать тестовый объект. В будущем мы заменим этот метод на взаимодействие с реальным сервером.

UserInfoActivity.java

public class UserInfoActivity extends AppCompatActivity {

    // Остальной код выше не изменился

    private User getUser() {
        return new User(
                1L,
                "http://i.imgur.com/DvpvklR.png",
                "DevColibri",
                "devcolibri",
                "Sample description",
                "USA",
                42,
                42
        );
    }
}

Видим, что в этом методе мы просто возвращаем в этом объекте все те же самые значения, которые мы использовали до этого.

Также нам надо немного изменить метод отображения данных пользователя. Надо добавить объект User как входной параметр и отображать данные из его полей:

UserInfoActivity.java

public class UserInfoActivity extends AppCompatActivity {

    // Остальной код выше не изменился

    private void displayUserInfo(User user) {
        Picasso.with(this).load(user.getImageUrl()).into(userImageView);
        nameTextView.setText(user.getName());
        nickTextView.setText(user.getNick());
        descriptionTextView.setText(user.getDescription());
        locationTextView.setText(user.getLocation());

        String followingCount = String.valueOf(user.getFollowingCount());
        followingCountTextView.setText(followingCount);

        String followersCount = String.valueOf(user.getFollowersCount());
        followersCountTextView.setText(followersCount);
    }

    // Остальной код ниже не изменился

}

Здесь практически ничего не изменилось. Единственное, что поля followingCount и followersCount имеют тип int, а метод setText() принимает только String входным параметром. Поэтому нам нужно сделать явное приведение к типу String.

Добавим ещё один метод loadUserInfo(), который будет вызывать метод getUser() и передавать его результат в метод displayUserInfo(user).

Итоговый код нашей UserInfoActivity:

UserInfoActivity.java

package colibri.dev.com.colibritweet;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;
import android.widget.TextView;

import com.squareup.picasso.Picasso;

import colibri.dev.com.colibritweet.pojo.User;

public class UserInfoActivity extends AppCompatActivity {
    private ImageView userImageView;
    private TextView nameTextView;
    private TextView nickTextView;
    private TextView descriptionTextView;
    private TextView locationTextView;
    private TextView followingCountTextView;
    private TextView followersCountTextView;

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

        userImageView = findViewById(R.id.user_image_view);
        nameTextView = findViewById(R.id.user_name_text_view);
        nickTextView = findViewById(R.id.user_nick_text_view);
        descriptionTextView = findViewById(R.id.user_description_text_view);
        locationTextView = findViewById(R.id.user_location_text_view);
        followingCountTextView = findViewById(R.id.following_count_text_view);
        followersCountTextView = findViewById(R.id.followers_count_text_view);

        loadUserInfo();
    }

    private void loadUserInfo() {
        User user = getUser();
        displayUserInfo(user);
    }

    private void displayUserInfo(User user) {
        Picasso.with(this).load(user.getImageUrl()).into(userImageView);
        nameTextView.setText(user.getName());
        nickTextView.setText(user.getNick());
        descriptionTextView.setText(user.getDescription());
        locationTextView.setText(user.getLocation());

        String followingCount = String.valueOf(user.getFollowingCount());
        followingCountTextView.setText(followingCount);

        String followersCount = String.valueOf(user.getFollowersCount());
        followersCountTextView.setText(followersCount);
    }

    private User getUser() {
        return new User(
                1L,
                "http://i.imgur.com/DvpvklR.png",
                "DevColibri",
                "devcolibri",
                "Sample description",
                "USA",
                42,
                42
        );
    }
}

Запустим приложение и увидим, что все поля объекта успешно отобразились:

Result.png

Подведём итоги:

  • При реальной работе приложения данные всегда приходят из java кода. Поэтому необходимо объявлять наши View в java коде и заполнять их информацией из реальных объектов.
  • Для удобного хранения данных используют POJO объекты. Они повышают уровень абстракции и позволяют хранить данные в виде, приближенном к реальному миру.

Полезные материалы:

Полный листинг изменений кода:

Code diff

УВИДЕТЬ ВСЕ Добавить заметку
ВЫ
Добавить ваш комментарий