Несколько дней назад я начал разработку простого чата для Android. Я решил использовать Firebase — простую в использовании базу данных, работающую в режиме реального времени, которая хранит свои данные в формате JSON. Несмотря на то, что Firebase предоставляет полную документацию по API и использованию, я обнаружил, что при попытке применить ее к шаблону архитектуры, такому как MVP, мне не хватает подробностей, поэтому я решил попробовать и объяснить, как я понимаю реализацию на Android. Я также расширил шаблон MVP новым слоем, специально для Firebase — Interactors.
Создание базового шаблона Firebase
Я не буду вдаваться в подробности создания учетной записи Firebase или «5-минутного быстрого старта», я просто перейду непосредственно к реализации.Сначала мы посмотрим, какие каталоги нам нужны в нашем шаблоне Firebase, например — Firebase создает пользователей в отдельной базе данных, и при создании хранимой информацией является адрес электронной почты, пароль (который вы не можете увидеть напрямую) и уникальный UID (случайно сгенерированный ключ, который хранится пользователем на протяжении всего его жизненного цикла), поэтому, если бы мы захотели сохранить имя пользователя, мы бы не смогли этого сделать.
Вот почему нам нужен каталог «Пользователи» в нашем шаблоне, который будет содержать имя пользователя и, возможно, аватар, чтобы мы могли хранить некоторую конкретную информацию.
У нас также может быть каталог под названием currentUsers, который будет содержать всех пользователей, которые в данный момент вошли в наше приложение чата.
Нам обязательно понадобится папка «Сообщения» для хранения наших сообщений.
Итак, наши три каталога — «Пользователи», «Текущие пользователи», «Сообщения»… Ссылки на них выглядят так: "https:// /текущиепользователи/" "https:// /Пользователи/" "https:// /Сообщения/" Это ссылки на каталоги, которые мы используем, когда хотим добавить/получить данные, и, по сути, все, что нам понадобится для запуска пользователя и системы сообщений.
Перейдем к реальному разговору с Android. Если вы импортировали зависимость Firebase в Gradle, вам должны быть доступны все функции клиента Firebase. Наше приложение чата будет иметь четыре экрана:
- Главный экран для выбора варианта входа (Войти или Регистрация) и отображения количества зарегистрированных пользователей.
- Вход для аутентификации пользователя
- ?Экран регистрации, на котором мы создаем новый экран «Пользователи — Чат» (на котором может отображаться фрагмент чата или фрагмент ListOfUsers)
Главный кран
Здесь мы ищем точку входа, которую хочет пользователь (регистрация или вход) и отображаем количество текущих пользователей в TextView.ГлавнаяДействительностьПредставитель :
ГлавныйИнтерактор:public class MainActivityPresenterImpl implements MainPresenter { private final MainView mainView; private final MainInteractor interactor; public MainActivityPresenterImpl(MainView view) { this.mainView = view; interactor = new MainInteractor(this); } @Override public void receiveRequest() { interactor.receiveRequest(); } @Override public String getNumberOfUsers(long numberOfUsers) { return "Online users: " + String.valueOf(numberOfUsers); } @Override public void sendNumberOfChildren(long number) { mainView.setNumberOfUsersTextView(getNumberOfUsers(number)); } }
public class MainInteractor implements MInteractor {
private final Firebase mainRef = new Firebase("https://<your-firebase>/currentUsers");
private final MainPresenter presenter;
public MainInteractor(MainPresenter pre) {
this.presenter = pre;
}
@Override
public void receiveRequest() {
mainRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
presenter.sendNumberOfChildren(dataSnapshot.getChildrenCount());
});
}
}
Что тут происходит? Во взаимодействии у нас есть ссылка Firebase, параметром-конструктором которой является ссылка (каталог currentUsers), и мы добавляем к ссылке прослушиватель, который делает один запрос к каталогу currentUsers Firebase и получает DataSnapshot — специальную функцию Firebase. Снимок по сути представляет собой список всех данных объектов в указанном каталоге, поэтому, если мы выполним dataSnapshot.getChildrenCount(), мы просто получим количество объектов, находящихся в данный момент в каталоге, которое равно количеству пользователей в сети! Мы отображаем его в TextView, и пользователь может видеть, сколько его коллег находятся в сети.
Довольно просто, но мощно, поскольку мы используем этот принцип запросов данных во всех аспектах взаимодействия с нашей Firebase.
Экнопка регистрации
Мы видели код главного экрана в предыдущем разделе, но вот как он выглядит. Кроме того, нажав «Зарегистрироваться», мы пройдем трехэтапный процесс: сначала мы выберем имя пользователя, которое, если оно появится, отобразит ошибку, в противном случае мы перейдем к фрагменту Emoji, в котором выберем свой собственный «аватар», затем мы перейдем к экрану «Сведения об учетной записи», на котором завершим регистрацию, если электронное письмо не будет отправлено, и в этом случае мы также получим сообщение об ошибке, поэтому вот экраны:У нас есть несколько простых EditTexts: один для имени пользователя, один для электронной почты и один для пароля.
Сетка смайлов на выбор (в настоящее время добавляется одна строка) и индикатор выполнения для отображения анимации вращения во время аутентификации.
Кнопка Register принимает значения, объединенные во Fragments, и отправляет их презентатору: public class FirebaseUserRegisterPresenterImpl implements FirebaseUserRegisterPresenter {
private final RegisterView registerView;
private final RegisterInteractor interactor;
public FirebaseUserRegisterPresenterImpl(RegisterView view) {
this.registerView = view;
this.interactor = new RegisterInteractor(this);
}
@Override
public void receiveRegisterRequest(String username, String email, String password, String emoji) {
interactor.receiveRegisterRequest(username, email, password, emoji);
registerView.spinProgressBar();
}
@Override
public void onFailure() {
registerView.onFailure();
registerView.stopProgressBar();
}
@Override
public void onSuccess() {
registerView.onSuccess();
registerView.stopProgressBar();
}
}
Интерактор:
public class RegisterInteractor implements RInteractor {
private Firebase userRef = new Firebase("https://<your-firebase>/Users/");
private final FirebaseUserRegisterPresenter presenter;
public RegisterInteractor(FirebaseUserRegisterPresenter pre) {
this.presenter = pre;
}
@Override
public void receiveRegisterRequest(final String username, String email, String password, final String emoji) {
userRef.createUser(email, password, new Firebase.ValueResultHandler<Map<String, Object>>() {
@Override
public void onSuccess(Map<String, Object> stringObjectMap) {
String uid = stringObjectMap.get("uid").
toString();
userRef = new Firebase("https://<your-firebase>/Users/" + uid);
userRef.setValue(createUser(username, emoji));
presenter.onSuccess();
}
@Override
public void onError(FirebaseError firebaseError) {
presenter.onFailure();
}
});
}
@Override
public Map<String, Object> createUser(String username, String emoji) {
Map<String, Object> user = new HashMap<>();
user.put("username", username);
user.put("emoji", emoji);
return user;
}
}
Здесь у нас есть несколько новых функций: - методы .
createUser(), .
push() и .
setValue() - пользовательский UID .
createUser() — создает пользователей! В отдельной базе данных, поэтому, когда мы создаем пользователя, нам также необходимо создать его объект в каталоге /Users (для его поиска).
Это делается нажатием кнопки «Нажатие».
Указанный .
push() «проталкивает» глубже в каталог, создавая подкаталог со случайно сгенерированным ключом для его имени, но перед этим мы добавляем UID к ссылке, чтобы мы могли сравнивать каталоги с UID пользователей.
UID — это случайно сгенерированный ключ, и, используя его в качестве имени подкаталога (и параметра в объекте User), мы можем позже определить, какое имя пользователя соответствует конкретному UID, и получить имя пользователя после входа в систему или даже удалить дочерний элемент currentUsers (выводит пользователя из системы).
Метод .
setValue() добавляет объект (или объекты) в каталог, поэтому мы можем просто хранить любые данные, которые захотим.
Экран входа в систему
Интерфейс экрана входа в систему довольно прост: два EditTexts (адрес электронной почты и пароль) и кнопка входа в систему, а также индикатор выполнения, чтобы немного оживить ситуацию.
Что происходит, когда пользователь нажимает «Войти»?
«Это сложная часть: мы знаем, что наши пользователи находятся в отдельной базе данных, поэтому, когда мы регистрируем пользователя, как мы узнаем, какое имя пользователя он или она использует? Как упоминалось ранее, в этом вся цель каталога /Users. Кроме того, назвав его по UID пользователя, мы можем просто искать каталог с соответствующим UID (если, например, мы хотим экстраполировать определенные фрагменты информации от конкретного пользователя).Кроме того, если мы назовем объекты UID, мы сможем ввести объект с указанным UID и удалить его в onTestroy() активности чата — очень простой способ зарегистрировать пользователя.
Вход в систему Ведущий: public class FirebaseLoginPresenterImpl implements FirebaseLoginPresenter {
private final LoginView loginView;
private final LoginInteractor interactor;
public FirebaseLoginPresenterImpl(LoginView view) {
this.loginView = view;
interactor = new LoginInteractor(this);
}
@Override
public void receiveUserLogin(String email, String password) {
loginView.spinProgressBar();
interactor.attemptToLogIn(email, password);
}
@Override
public void onFailure() {
loginView.stopProgressBar();
loginView.onFailure();
}
@Override
public void onSuccess(String user, String uid) {
loginView.stopProgressBar();
loginView.logTheUserIn(user, uid);
}
}
Он получает адрес электронной почты и пароль, показывает полосу прокрутки до завершения запроса и вызывает методы View, получая результат:
- При успешном входе имя пользователя и UID пользователя отправляются в намерение, которое запускает вход в ChatActivity. При сбое пользователь предупреждается всплывающим сообщением.
Полученные AuthData по умолчанию и служит для отображения некоторых конкретных данных Firebase о пользователе (например, UID, специальный ключ, сгенерированный при аутентификации.
)
Экран чата
ChatActivity использует 2 фрагмента: один для службы обмена сообщениями и один для отображения списка активных пользователей.Если щелкнуть значок меню один раз, фрагмент сообщений будет заменен фрагментом списка, а повторное нажатие откроет BackStack (и вернется!).
Проблема здесь в том, что мы получаем все наши данные из Firebase, а это означает, что мы не можем реализовать Firebase в наших представлениях, но адаптеры ListView/RecyclerView также являются компонентами Android View, так что же нам делать дальше?
Ответ еще раз — MVP (+Интеракторы)! Хорошая архитектура отражается в компонентах, которые она реализует, а это значит, что мы также можем написать наши адаптеры в MVP, то есть в компоненте View, у которого есть презентер, который отправляет новые значения элементам ListView (и запрашивает указанные значения у Interactor) Поскольку значения генерируются Interactor, который имеет ссылку на Firebase — мы можем отделить Android от Java — от Backend.
Адаптер
: public class CustomMessageRecyclerAdapter extends RecyclerView.Adapter<CustomMessageRecyclerAdapter.ViewHolder> implements MessageAdapterView {
private final ArrayList<Message> mMessageList = new ArrayList<>();
private final String user;
private final MessagePresenterImpl presenter;
public CustomMessageRecyclerAdapter(String username) {
this.user = username;
presenter = new MessagePresenterImpl(this);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).
inflate(R.layout.chat_message, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Message current = mMessageList.get(position);
if (current.getAuthor().
equals(user)) {
holder.mAuthorTextView.setText("You");
} else {
holder.mAuthorTextView.setText(current.getAuthor());
}
holder.mMessageTextView.setText(current.getMessage());
holder.mEmojiTextView.setText(current.getEmoji());
}
@Override
public int getItemCount() {
return mMessageList.size();
}
@Override
public void addItem(Message message) {
mMessageList.add(message);
notifyDataSetChanged();
}
@Override
public void request() {
presenter.requestMessages();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView mAuthorTextView;
private TextView mMessageTextView;
private TextView mEmojiTextView;
public ViewHolder(View itemView) {
super(itemView);
mAuthorTextView = (TextView) itemView.findViewById(R.id.message_author);
mMessageTextView = (TextView) itemView.findViewById(R.id.message_value);
mEmojiTextView = (TextView) itemView.findViewById(R.id.message_emoji);
}
}
}
Это очень просто: у нас есть метод, который раздувает наш ViewHolder, который заполняет указанный держатель, метод запроса сообщения из Firebase и метод, который добавляет сообщение в ArrayList, если есть новое сообщение для отображения.
Ведущий:
public class MessagePresenterImpl implements MessagePresenter {
private final MessageAdapterView adapterView;
private final MessageInteractor interactor;
public MessagePresenterImpl(MessageAdapterView view) {
this.adapterView = view;
this.interactor = new MessageInteractor(this);
}
@Override
public void sendMessageToAdapter(Message message) {
adapterView.addItem(message);
}
@Override
public void requestMessages() {
interactor.request();
}
}
Интерактор :
public class MessageInteractor {
private final MessagePresenter presenter;
private final Firebase mMessagesRef = new Firebase("https://<your-firebase>/messages");
private final Query mMessageQuery;
public MessageInteractor(MessagePresenter pre) {
this.presenter = pre;
this.mMessageQuery = mMessagesRef.orderByValue().
limitToLast(100);
}
public void request() {
mMessageQuery.addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
presenter.sendMessageToAdapter(dataSnapshot.getValue(Message.class));
}
//some more auto-generated methods
Адаптеру нужно новое сообщение, он говорит Presenter запрашивать сообщения, но это не работа Presenter, поэтому он говорит Interactor запрашивать их у Firebase, делая это, мы получаем чистую структуру и поток данных, полностью независимые, поэтому изменить представление мы не можем, все нужно изменить, мы просто настраиваем POJO данных, презентаторам и интеракторам не нужно менять то, что они делают, запросы остаются прежними! Поэтому, если мы изменим объем данных, просто добавим в POJO больше полей, если мы хотим отображать их по-другому, просто измените представление (добавив больше виджетов).
Запрос просто означает запрос, .
orderByValue() означает, что мы получаем туда объекты (значения), .
limitToLast(100) означает, что мы всегда получаем последние 100 сообщений.
Хотя если чат активен какое-то время, то все сообщения (даже после 100) будут отображаться до тех пор, пока фрагмент сообщения не будет уничтожен/перезапущен.
Также в нашей onDestroy ChatActivity мы отправляем UID интерактору (через Presenter), чтобы удалить пользователя из currentUsers (выйти из него).
public class ChatLoginInteractor implements CLoginInteractor {
@Override
public void logTheUserOut(String uid) {
Firebase userRef = new Firebase("https://<your-firebase>/currentUsers/" + uid);
userRef.removeValue(); //removes the Child from Firebase
}
}
Как это работает, шаг за шагом .
Библиотека Firebase для Android очень хорошо построена, документацию немного сложно понять, но основные принципы легко понять, если покопаться и попытаться собрать все воедино.
— Ссылка Firebase — это просто ссылка на каталог, в котором вы хотите внести изменения, выполнить запросы или просто добавить новые данные .
Слушатели предоставляют нам «Rx-подобные» функции, они постоянно следят за добавлением новых пользователей (каждый объект в каталоге является дочерним), и мы можем работать с их данными.
DataSnapshot — это список текущих значений в одном каталоге.
AuthData похожа на пакет всех данных для конкретного пользователя/запроса, UID, уникального ключа.
- Firebase использует синтаксический анализ Джексона, поэтому для генерации вашим POJO нужны пустые конструкторы и установщики.
- вам действительно не нужны специальные клиенты REST, поскольку функция DataSnapshot может выполнять весь анализ данных, используя .
getValue(POJO.class)
- В режиме реального времени.
Все запросы и клики в Firebase выполняются чрезвычайно быстро, поскольку все данные форматируются как объекты JSON.
Заключение :
Firebase — чрезвычайно мощный инструмент для простых серверных баз данных, он очень быстрый и простой в использовании в небольших проектах, но его можно использовать даже для более сложных приложений, таких как это приложение для чата.Он кроссплатформенный, поэтому вы можете создавать приложения Firebase для Android, iOS и JS с полной поддержкой (я полагаю, JS поддерживает Angular, React и Node) и использовать один и тот же шаблон Firebase на всех трех основных платформах.
P.S. Я не гарантирую полную функциональность в течение длительного времени, так как FireBase постоянно обновляется и изменяется.
Внесение нескольких изменений будет иметь значение.
Теги: #Чат на Android #Чат Android #Хочу как Дуров #underdog #open source #java #Разработка под Android
-
Армения
19 Oct, 24 -
Самое Бюджетное Видеонаблюдение В Стране
19 Oct, 24 -
Многоразовый Космический Корабль
19 Oct, 24 -
Wal В Postgresql: 4. Настройки Журнала
19 Oct, 24 -
Деревянные Игрушки, Часть Десятая - 1996 Г.
19 Oct, 24 -
Упростить! = Улучшить
19 Oct, 24 -
Как Отправить Отзыв Об Ie8
19 Oct, 24