Использование Google FireBase для создания простого чата на Android

в 14:21, , рубрики: Android чат, java, open source, недограм, Разработка под android, хочу как Дуров, Чат на Android

Несколько дней назад я начал разработку простейшего чата под Android, я решил использовать Firebase — простую в использовании базу данных в режиме реального времени, которая хранит свои данные в формате JSON. Несмотря на то, что Firebase предоставляет полный API-интерфейс и документацию по использованию, я обнаружил, что у него не хватает деталей при попытке применить его к шаблону архитектуры, например MVP, поэтому я решил попробовать и объяснить, как я понял реализацию на Android. Также я расширил шаблон MVP новым слоем, явно для Firebase — Interactors.

Создание базового шаблона Firebase

Я не буду вдаваться в подробности о создании учетной записи в Firebase, а также о «5-минутном быстром запуске», я просто пойду прямо в реализацию.

Сначала мы рассмотрим, какие каталоги нам нужны в нашем шаблоне Firebase, например — Firebase создает пользователей в отдельной базе данных, а при создании хранимой информации — это электронное письмо, пароль (который вы не можете наблюдать напрямую) и уникальный UID (случайно сгенерированный ключ, который придерживается пользователя в течение всего его жизненного цикла), поэтому, если мы хотим сохранить имя пользователя, мы не смогли бы… Вот почему нам нужен «Пользователи» в качестве каталога в нашем шаблоне, который будет содержать имя пользователя и, возможно, аватар, чтобы мы могли хранить определенную конкретную информацию.

У нас также может быть каталог с именем curentUsers, который будет содержать все пользователи, которые в настоящее время вошли в наше приложение для чата. Нам определенно нужна папка «Сообщения» для хранения наших сообщений.
Итак, наши три каталога — это Пользователи, currentUsers, Messages…

Ссылки на них выглядят следующим образом:

«https: // <your-firebase> / currentUsers /"
«https: // <your-firebase> / Users /»
«https: // <ваш-firebase> / сообщения /»

Это ссылки на каталоги, которые мы используем, когда хотим добавлять / извлекать данные, и в основном все, что нам понадобится для работы системы пользователей и сообщений.

Перейдем к реальному разговору с Android. Если вы импортировали зависимость Firebase в Gradle, у вас должны быть доступны все функции клиента Firebase… В нашем чат-приложении будет четыре экрана:

  • Главный экран для выбора параметра входа (Вход или Регистрация) и отображение количества зарегистрированных пользователей
  • Вход для аутентификации пользователя
  • Экран регистрации, в котором мы создаем новый  экран « Пользователи — Чат» (который может отображать фрагмент чата или фрагмент ListOfUsers)

Главный Экран

Здесь мы ищем ту точку входа, которую хочет пользователь (зарегистрировать или войти в систему), и отобразим количество текущих пользователей в TextView.

MainActivityPresenter:

  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));
    }
} 

The MainInteractor:

  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), и мы добавляем слушателя к ссылке, которая отправляет один запрос в каталог Firebase currentUsers и получает DataSnapshot — специальную функцию Firebase… Снимок по существу представляет собой список всех объектов данных в указанном каталоге, поэтому, если мы делаем dataSnapshot.getChildrenCount (), мы просто получаем количество объектов, находящихся в данный момент в каталоге, что равно числу пользователей онлайн! Мы показываем его в TextView, и пользователь видит, сколько его сверстников в сети. Довольно простой, но мощный, поскольку мы используем этот принцип запросов данных во всех аспектах общения с нашей Firebase.

Экран регистрации

Мы видели код для главного экрана в предыдущем разделе, но вот как он выглядит. Кроме того, нажав регистр, мы пройдем через трехэтапный процесс, сначала выберем Username, которое, если оно отобразится, отображает ошибку, в противном случае мы перейдем к фрагменту Emoji, в котором мы выберем наш собственный «аватар», затем перейдем к учетной записи подробный экран, в котором мы завершаем нашу регистрацию, если не отправлено электронное письмо, и в этом случае мы также получаем ошибку, так что вот экраны:

image

У нас есть несколько простых EditTexts, один для Username, один для E-mail и один для пароля. Сетка из emoji на выбор (в настоящее время одна строка добавит больше) и индикатор выполнения для отображения анимацию вращения во время проверки подлинности. Кнопка «Регистр» принимает значения, объединенные в «Фрагменты», и отправляет их в презентатор:

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();
    }
}

Interactor:

   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 (чтобы посмотреть его).
Это делается нажатием «Pushing». Указанный .push () «толкает» глубже в каталог, создавая подкаталог со случайным сгенерированным ключом для его имени, но до этого мы присоединяем UID к ссылке, поэтому мы можем сравните каталоги с UID пользователей. UID является случайным образом генерируемым ключом и, используя его как имя подкаталога (и параметр в объекте User), мы можем позже определить, какое имя пользователя соответствует определенному UID, и получить имя пользователя после входа в систему или даже удалить Child of currentUsers (выведите пользователя из системы).

Метод .setValue () добавляет объект (или объекты) в каталог, поэтому мы можем просто хранить любые данные, которые мы хотим.

Экран входа в систему

Интерфейс экрана входа в систему довольно прост: два EditTexts (адрес электронной почты и пароль) и кнопка входа в систему, а также панель индикаторов выполнения, чтобы немного оживить процесс.

Что происходит, когда пользователь нажимает Вход в систему?

Эта часть сложна, мы знаем, что наши пользователи находятся в отдельной базе данных, поэтому, когда мы регистрируем пользователя, как нам узнать, по какому имени пользователя он или она идет?

Вот вся цель каталога / Users, как упоминалось ранее. Также, назвав его после пользовательского UID, мы можем просто искать каталог с соответствующим UID (если, например, мы хотим экстраполировать определенные части информации от конкретного пользователя). Также, если мы назовем объекты UID, мы можем ввести объект с указанным UID и удалить его в onTestroy () активности чата — очень простой способ регистрации пользователя.

Login Presenter:

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);
    }
}

Он получает электронное письмо и пароль, показывает полосу прокрутки до тех пор, пока запрос не будет завершен, и вызовет методы «Вид», учитывая результат:

  • Успешный вход в систему отправляет имя пользователя пользователя, а UID — в намерение, в котором запускается вход в  систему ChatActivity — Failed предупреждает пользователя с помощью Toast.

Если аутентификация пользователя прошла успешно, мы получаем имя пользователя для указанного пользователя и отправляем ее на экран чата, но до этого мы добавляем пользователя в каталог / currentUsers, чтобы мы могли просто видеть, кто вошел в систему. Получен AuthData по умолчанию и служит для отображения некоторых конкретных данных Firebase о пользователе (например, UID, специальный ключ, сгенерированный аутентификацией ..)

Экран чата

В ChatActivity используются 2 фрагмента, один для службы обмена сообщениями, и один для отображения списка активных пользователей. Нажав на значок меню меню один раз, мы заменим фрагмент сообщений фрагментом списка, и, щелкнув его еще раз, мы выставим BackStack (и вернемся!).

image

Проблема здесь в том, что мы получаем все наши данные от Firebase, то есть мы не можем реализовать Firebase в наших представлениях, но адаптеры ListView / RecyclerView также являются компонентами Android View, так как мы здесь продолжаем?

Ответ еще раз — MVP (+ Interactors)! Хорошая архитектура отражает себя в компонентах, которые она реализована, что означает, что мы также можем написать наши адаптеры в MVP, являющиеся компонентом «Вид», в которых есть презентатор, который отправляет новые значения в элементы ListView (и запрашивает указанные значения из Interactor), Поскольку значения генерируются Interactor, который имеет ссылку на Firebase — мы можем отделить Android от Java — от Backend.

Adapter

:

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, если есть новое сообщение для отображения.

Presenter:

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();
    }
}

Interactor:

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

Адаптеру требуется новое сообщение, оно сообщает Презентеру запрашивать сообщения, но это не работа Презентера, поэтому он сообщает Interactor запросить их у Firebase, сделав это, мы имеем чистую структуру и поток данных, полностью независимый, поэтому изменив представление, нам не нужно все менять, мы просто настраиваем POJO данных, докладчикам и интеракторам не нужно менять то, что они делают, запросы остаются неизменными! Поэтому, если мы переключим количество данных, просто добавьте больше полей в POJO, если мы хотим отобразить их по-другому, просто измените представление (добавив больше виджетов).

Запрос просто означает запрос, .orderByValue () означает, что мы получаем объекты (значения) там, .limitToLast (100) означает, что мы всегда получаем последние 100 сообщений. Хотя если чат активен некоторое время, все сообщения (даже после 100) будут отображаться до тех пор, пока фрагмент сообщения не будет уничтожен / перезапущен.

Также в нашем onDestroy ChatActivity мы отправляем UID интерактору (через презентатора), чтобы удалить пользователя из 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-like» функции, они постоянно наблюдают за добавлением новых детей (каждый объект в каталог является дочерним), и мы можем работать с их данными.
 DataSnapshot — это список текущих значений в одном каталоге.

 AuthData похожа на Bundle всех данных для определенного пользователя / запроса, UID, уникальный ключ…

  • Firebase использует разбор Джексона, поэтому вашим POJO нужны пустые конструкторы, а генерации / сеттеры
  • вам действительно не нужны специальные клиенты REST, так как функция DataSnapshot может выполнять весь синтаксический анализ данных с помощью .getValue (POJO.class)
  • В реальном времени… Все запросы и нажатия на Firebase чрезвычайно быстрые, поскольку все данные отформатированы как объекты JSON.

 Прежде чем использовать какие-либо ссылки на вашу Firebase, вы должны вызвать Firebase.setAndroidContext (this) в каждом методе onCreate ()

Вывод:

Firebase — чрезвычайно мощный инструмент для простых баз данных Backend, он очень быстр и прост в использовании на небольших проектах, но может использоваться даже для более сложных приложений, подобных этому чат-приложению.

Это кросс-платформа, поэтому вы можете создавать приложения Firebase для Android, iOS и JS с полной поддержкой (JS поддерживает Angular, React и Node, я считаю) и использовать один шаблон Firebase на всех трех основных платформах.

P.S. Я не гарантирую полную работоспособность на длительное время, так как FireBase постоянно обновляеться и изменяеться. Внесение нескольких правок изменит ситуацию.

Автор: Evgeniy Smirnov

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js