Эпилог.

Функциональность чата обязательна почти в каждом современном приложении, и у разработчиков интерфейса часто возникают проблемы с выполнением этой задачи со странными бэкенд-парнями :)

Что, если я скажу вам, что для этого вам не нужен бэкенд?

Что, если я скажу вам, что вы можете как минимум поговорить один на один за 15 минут?

Что, если я скажу вам, что вашим клиентам будет намного дешевле?

Наша цель:

Эпизод 1. Настройка

Что нам понадобится из пакетов?

  1. react-native-firebase - это будет наш бэкэнд и наш хост pub-sub (Websockets). Вы можете найти пакет здесь. Нам нужно будет инициализировать CocoaPods для приложения Firebase для iOS.
  2. lodash - просто чтобы наш код был чище. Вы можете найти пакет здесь.

Итак, начнем с инициализации пустого проекта:

react-native init SimpleChat

Теперь давайте сразу добавим необходимые пакеты:

yarn add lodash && yarn add firebase

ВАЖНО: пока не делайте react-native link!

Android

Официальное руководство по установке. Лучше использовать официальное руководство по установке, поскольку оно достаточно подробное.

iOS

Официальное руководство по установке, или вы можете прочитать дальше, мы сделаем это шаг за шагом.

Хорошо, поскольку Android установлен, мы можем начать с CocoaPods. Я предполагаю, что вы используете Mac или уже установили Ruby (да, он еще не умер). Если вы этого не сделаете, следуйте инструкциям здесь, чтобы установить RVM и Ruby.

Давайте инициализируем CocoaPods, подробнее, как установить CocoaPods, можно найти здесь.

Перейдите в папку /ios и запустите:

pod init

Вы увидите, что теперь в папке /ios есть Podfile (package.json аналог в CocoaPods мире) и SimpleChat.xcworkspace. Это означает, что в вашем проекте установлено CocoaPods.

ВАЖНО: Если вы используете Xcode, откройте свой проект, нажав SimpleChat.xcworkspace вместо .xcodeproj файл.

Теперь мы готовы к react-native link, перейдите в корень вашего проекта и выполните:

react-native link

В настоящее время эта команда имеет две цели:

  1. Это добавит Firebase/Core к вашему Podfile.
  2. Он обновит этап сборки и свяжет библиотеку Firebase с вашим проектом.

Нам по-прежнему нужны две дополнительные библиотеки из пакета Firebase:

  1. Firebase/Firestore - База данных Firebase
  2. Firebase/Auth - авторизация с помощью Firebase (например, если у вас есть бэкэнд, вам нет смысла его устанавливать, но в этом руководстве мне это нужно для идентификации пользователей)

Итак, давайте обновим наш /ios/Podfile и добавим несколько строк кода. Теперь это должно выглядеть так:

platform :ios, '9.0'

target 'SimpleChat' do
  pod 'Firebase/Core', '~> 5.20.2'
  pod 'Firebase/Firestore', '~> 5.20.2'
  pod 'Firebase/Auth', '~> 5.20.2'
end

ПРИМЕЧАНИЕ. Не забудьте удалить ненужное из файла, если вы знаете, что делаете.

Когда react-native link закончится и вы добавили необходимые пакеты, теперь вам нужно перейти в папку /ios и запустить:

pod install

Теперь нам нужно изменить наш AppDelegate.m и добавить две строки кода, сначала рядом с вами import:

#import <Firebase.h>

Второй внутри didFinishLaunchingWithOptions:(NSDictionary *)launchOptions сразу после определения:

[FIRApp configure];

Все библиотеки установлены. Теперь нам нужно настроить само приложение Firebase.

Эпизод 2. Приложение Firebase

Перейти в консоль Firebase. Если у вас нет учетной записи, создайте ее. Это бесплатно.

Создайте новый проект и введите его название:

Теперь нам нужно настроить Authentication, опять же, если у вас есть бэкэнд, этот шаг вам не нужен.

Перейдите к AuthenticationSetup sign-in method и выберите способ получения, который соответствует вашим потребностям. Для этого урока будет достаточно анонимной аутентификации:

Теперь нам нужно включить Firestore, перейти к DatabaseCreate database› Выбрать режим (для этого урока - я использую test mode, этого будет достаточно). Если вы все сделали правильно, вы должны увидеть страницу управления базой данных:

Отлично, сервисы готовы, теперь давайте создадим приложение для iOS и Android. Перейдите к Project Overview ›Выберите ОС, которая вам нужна (в этом уроке у меня есть только проект iOS, но он одинаково хорошо работает как на Android, так и на iOS.

Для обеих ОС просто следуйте инструкциям в консоли firebase (за исключением шага, который вы уже сделали, после успешного завершения установки вы получите файл конфигурации.

Android

Поместите свой google-service.json в /android/app:

iOS

Поместите свой GoogleService-Info.plist в корень Xcode:

Теперь мы настроены. Давайте код.

Эпизод 3. Разметка

Я буду писать все с React Hooks, но у вас будет пример без хуков в репозитории.

Наше дерево проекта будет выглядеть так:

|- /SimpleChat
|  |- /src
|  |  |- /components
|  |  |  |- /common
|  |  |  |  |- /Button
|  |  |  |  |  |- /index.js
|  |  |  |  |  |- /styles.js
|  |  |  |  |- /Loader
|  |  |  |  |  |- /index.js
|  |  |  |  |  |- /styles.js
|  |  |  |- /HooksExample
|  |  |  |  |- /index.js
|  |  |  |  |- /reducers.js
|  |  |  |- /Input
|  |  |  |  |- /index.js
|  |  |  |  |- /styles.js
|  |  |  |- /Message
|  |  |  |  |- /index.js
|  |  |  |  |- /styles.js
|  |  |- /constants
|  |  |  |- /collection.js
|  |  |  |- /index.js
|  |  |- /services
|  |  |  |- /index.js
|  |  |  |- /FirebaseService.js
|  |  |- /styles
|  |  |  |- /chat-room.js
|  |  |  |- /colors.js
|  |  |  |- /index.js
|  |  |- /index.js
|  |  |- /contexts.js

Немного подробностей о структуре:

  1. /src/context.js - это место, куда мы собираемся поместить весь наш контекст (например, currentUser).
  2. /src/styles/chat-room.js - это просто стили, так как я собираюсь повторно использовать их в примере компонента класса, а стили здесь помещены только для СУХОЙ (не повторяйте себя). Как обычно, вы можете поместить под себя папку с компонентами, в данном случае, например, HooksExample.
  3. /src/components/HooksExample/reducers.js - это место, где я кладу сложную логику с крючками.
  4. /src/services/FirebaseService.js - это место, где мы будем общаться с firebase.
  5. /src/constants/collections.js - это место, где мы сохраняем имя нашей коллекции firebase

Начнем с нашего основного компонента. В следующей части мы напишем нашу логику.

Так что сейчас мы будем работать без разметки компонентов. Обычно все чаты имеют две части экрана: сообщения и кнопку ввода плюс для создания сообщений.

Мы собираемся создать наш главный компонент, давайте назовем его HooksExample в этом руководстве, но собственное имя для него будет Chat component. Он будет отображать FlatList с нашими сообщениями и Input компонент, в который мы собираемся заполнить наши сообщения и отправить их.

Итак, начнем с нашего Input компонента. Позволяет код:

import React, { useCallback, useState } from 'react'
import { View, TextInput } from 'react-native'

import Button from '../common/Button'

import styles from './styles'

export default function Input () {
  const [message, setMessage] = useState('')

  const handlePress = useCallback(
    function () {
      // todo this
    },
    [message]
  )

  return (
    <View style={styles.container}>
      <View style={styles.inputContainer}>
        <TextInput style={styles.input} value={message} onChangeText={setMessage} placeholder="Write you message" />
      </View>

      <Button text="Send" onPress={handlePress} />
    </View>
  )
}

И наши стили:

import { StyleSheet } from 'react-native'

import { COLORS } from '../../styles'

export default StyleSheet.create({
  container: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-around',
    width: '100%'
  },
  inputContainer: {
    width: '70%'
  },
  input: {
    height: 40,
    borderColor: COLORS.GREY,
    borderWidth: 1,
    borderRadius: 3,
    flexDirection: 'row',
    paddingHorizontal: 10
  }
})

Вот что мы получим схематично:

Теперь давайте нацелимся на компонент Message:

import React from 'react'
import { View, Text } from 'react-native'

import { styles, flattenedStyles } from './styles'

export default function Message ({ message, side }) {
  const isLeftSide = side === 'left'

  const containerStyles = isLeftSide ? styles.container : flattenedStyles.container
  const textContainerStyles = isLeftSide ? styles.textContainer : flattenedStyles.textContainer
  const textStyles = isLeftSide ? flattenedStyles.leftText : flattenedStyles.rightText

  return (
    <View style={containerStyles}>
      <View style={textContainerStyles}>
        <Text style={textStyles}>
          {message}
        </Text>
      </View>
    </View>
  )
}

Этот компонент принимает два props:

  1. message - текст, строка
  2. side - строка, это будет left или right. И это поможет компоненту выбрать стили для компонента сообщения.

Нам нужно применить разные стили для левой и правой стороны. Вот как это выглядит в стилях:

import { StyleSheet } from 'react-native'

import { COLORS } from '../../styles'

const styles = StyleSheet.create({
  container: {
    width: '100%',
    paddingVertical: 3,
    paddingHorizontal: 10,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'flex-start'
  },
  textContainer: {
    width: 160,
    backgroundColor: COLORS.GREY,

    borderRadius: 40,
    paddingHorizontal: 15,
    paddingVertical: 12,
    marginLeft: 10
  },
  rightContainer: {
    justifyContent: 'flex-end'
  },
  rightTextContainer: {
    backgroundColor: COLORS.PRIMARY,
    marginRight: 10
  },
  leftText: {
    textAlign: 'left'
  },
  rightText: {
    textAlign: 'right'
  },
  text: {
    fontSize: 12
  }
})

const flattenedStyles = {
  container: StyleSheet.flatten([styles.container, styles.rightContainer]),
  textContainer: StyleSheet.flatten([styles.textContainer, styles.rightTextContainer]),
  leftText: StyleSheet.flatten([styles.leftText, styles.text]),
  rightText: StyleSheet.flatten([styles.rightText, styles.text])
}

export {
  styles,
  flattenedStyles
}

Теперь причина, по которой я создаю объект с именем flattenedStyles, заключается в создании массива стилей с идентификатором. Этот механизм предоставляется нам StyleSheet и помогает выполнять простую оптимизацию.

У нас есть и Message, и Input компоненты, и мы должны объединить их в наш HooksExample компонент.

import React from 'react'
import { FlatList, SafeAreaView, View } from 'react-native'

import Input from '../Input'
import Message from '../Message'
import { chatRoomStyles as styles } from '../../styles'

export default function HooksExample () {
  const mock = [
    { id: 1, message: 'Hello', side: 'left' }, 
    { id: 2, message: 'Hi!', side: 'right' }
  ]
  return (
    <SafeAreaView>
      <View style={styles.messagesContainer}>
        <FlatList
          inverted
          data={}
          keyExtractor={function (item) {
            return item.id
          }}
          renderItem={function ({ item }) {
            return (
              <Message side={data.side} message={data.message} />
            )
          }}
        />
      </View>

      <View style={styles.inputContainer}>
        <Input />
      </View>
    </SafeAreaView>
  )
}

И наши стили для HooksExample компонента:

import { StyleSheet } from 'react-native'

import COLORS from './colors'

export default StyleSheet.create({
  messagesContainer: {
    height: '100%',
    paddingBottom: 100
  },
  inputContainer: {
    width: '100%',
    height: 100,
    position: 'absolute',
    bottom: 0,
    paddingVertical: 10,
    paddingLeft: 20

    borderTopWidth: 1,
    borderTopColor: COLORS.GREY
  }
})

Итак, в итоге мы получим наценку за наш чат

Пролог.

Наша разметка готова, и мы можем написать свою логику. Посетите Чат за 30 минут с ReactNative и Firebase, серьезно! Pt. 2 . продолжить чтение…

Ссылка на репозиторий

В проекте в репозитории уже настроено приложение Firebase, и вы можете клонировать этот репозиторий и поэкспериментировать.