Executando verificação de segurança...
-5

2 - React Native

2.1 Create a new React Native app (I used Expo)

Instructions in the official documentation

2.2 Install the dependencies

check the dependencies I used (package.json)
{
  "name": "YOUR APP NAME",
  "version": "1.0.0",
  "main": "expo-router/entry",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@react-native-community/netinfo": "11.3.1",
    "axios": "^1.7.2",
    "expo": "~51.0.11",
    "expo-constants": "~16.0.2",
    "expo-linking": "~6.3.1",
    "expo-router": "~3.5.15",
    "expo-status-bar": "~1.12.1",
    "laravel-echo": "^1.16.1",
    "pusher-js": "^8.4.0-rc2",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-native": "0.74.2",
    "react-native-safe-area-context": "4.10.1",
    "react-native-screens": "3.31.1",
    "react-native-web": "~0.19.10",
    "socket.io-client": "^4.7.5",
    "twrnc": "^4.2.0"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0",
    "react-native-dotenv": "^3.4.11"
  },
  "private": true
}

check this to configure react-native-dotenv
I just made this (babel.config.js):

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: [
      ["module:react-native-dotenv"],
      // {
      // envName: "APP_ENV",
      // moduleName: "@env",
      // path: ".env",
      // blocklist: null,
      // allowlist: null,
      // blacklist: null, // DEPRECATED
      // whitelist: null, // DEPRECATED
      // safe: false,
      // allowUndefined: true,
      // verbose: false,
      // },
    ],
  };
};

2.3 Setup .env

.env

# YOUR-SERVER-HOST:YOUR-SERVER-PORT
# (YOU CAN GET IT WHEN YOU RUN php artisan server --host=YOUR-LOCAL-IP)

BACKEND_API_URL=http://YOUR-SERVER-HOST:YOUR-SERVER-PORT/api

REVERB_APP_ID= # FIND IT IN .env FILE OF YOU LARAVEL APPLICATION
REVERB_APP_KEY=  # FIND IT IN .env FILE OF YOU LARAVEL APPLICATION
REVERB_APP_SECRET= # FIND IT IN .env FILE OF YOU LARAVEL APPLICATION
REVERB_HOST= # FIND IT IN .env FILE OF YOU LARAVEL APPLICATION
REVERB_PORT= # FIND IT IN .env FILE OF YOU LARAVEL APPLICATION
REVERB_SCHEME= # FIND IT IN .env FILE OF YOU LARAVEL APPLICATION

2.4 Setup services (API)

services/api

import axios from "axios";
import { BACKEND_API_URL } from "@env";

const api = axios.create({
  baseURL: BACKEND_API_URL,
  headers: {
    "Content-Type": "application/json",
  },
});

const setBearerToken = (token) => {
  api.defaults.headers.common["Authorization"] = `Bearer ${token}`;
};

export { api, setBearerToken };

services/auth

import { api } from "./api";

export const Auth = {
  async login(email, password) {
    const options = {
      method: "POST",
      url: "/login",
      data: {
        email,
        password,
      },
    };

    try {
      const response = await api.request(options);
      return response.data;
    } catch (error) {
      throw error;
    }
  },
};

2.5 Create Echo instance (our websocket client)

hooks/echo

import { useEffect, useState } from "react";
// import axios from "axios";
import { api as axios } from "../services/api";
import Echo from "laravel-echo";
import Pusher from "pusher-js/react-native";
import { REVERB_APP_KEY, REVERB_HOST, REVERB_PORT, REVERB_SCHEME } from "@env";

const useEcho = () => {
  const [echoInstance, setEchoInstance] = useState(null);

  useEffect(() => {
    //  Setup Pusher client
    const PusherClient = new Pusher(REVERB_APP_KEY, {
      wsHost: REVERB_HOST,
      wsPort: REVERB_PORT ?? 80,
      wssPort: REVERB_PORT ?? 443,
      forceTLS: (REVERB_SCHEME ?? "https") === "https",
      enabledTransports: ["ws", "wss"],
      disableStats: true,
      cluster: "mt1",
      authorizer: (channel, options) => {
        return {
          authorize: (socketId, callback) => {
            axios
              .post("/broadcasting/auth", {
                socket_id: socketId,
                channel_name: channel.name,
              })
              .then((response) => {
                callback(false, response.data);
              })
              .catch((error) => {
                callback(true, error);
              });
          },
        };
      },
    });

    // Create Echo instance
    const echo = new Echo({
      broadcaster: "reverb",
      client: PusherClient,
    });

    setEchoInstance(echo);

    // Cleanup on unmount
    return () => {
      if (echo) {
        echo.disconnect();
      }
    };
  }, []);

  return echoInstance;
};

export default useEcho;

2.6 Create chat component

components/chatInterface

// src/components/ChatInterface.js
import React, { useState, useEffect } from "react";
import {
  SafeAreaView,
  View,
  TextInput,
  TouchableOpacity,
  Text,
  FlatList,
  KeyboardAvoidingView,
  Platform,
} from "react-native";
import { style as tw } from "twrnc";
import useEcho from "../hooks/echo";
import { api } from "../services/api";

const ChatInterface = ({ user }) => {
  const [messages, setMessages] = useState([]);
  const [inputText, setInputText] = useState("");
  const [userData, setUserData] = useState(user);

  const echo = useEcho();

  const handleSend = async () => {
    if (inputText.trim()) {
      setInputText("");
      const options = {
        method: "POST",
        url: "/send-message",
        data: {
          /**I logged in with user 1 and 7.
           * then you configure this to be dynamic
           * (giving the user the power to choose who they want to chat with)
           * */
          user_id: userData.id === 1 ? 7 : 1, // receiver
          from: userData.id,
          message: inputText,
        },
      };

      try {
        const response = await api.request(options);
        if (response.status === 200) {
          const data = response.data.data;
          console.log("response", data);
          setMessages((prevMessages) => [
            ...prevMessages,
            {
              id: data?.message?.id,
              from: userData.id,
              message: data?.message?.message,
            },
          ]);
        }
      } catch (error) {
        console.error(error);
        // Handle error appropriately
      }
    }
  };

  function subscribeToChatChannel() {
    if (echo) {
      echo.private(`chat.${user?.id}`).listen("MessageSent", (e) => {
        console.log("real-time-event", {
          id: e.message?.id,
          message: e.message?.message,
          from: e.message?.from,
        });
        setMessages((prevMessages) => [
          ...prevMessages,
          {
            id: e.message?.id,
            message: e.message?.message,
            from: e.message?.from,
          },
        ]);
      });
    }
  }

  useEffect(() => {
    if (echo) {
      console.log("user", userData.id);
      subscribeToChatChannel();
    }
    return () => {
      if (echo && user) {
        echo.leaveChannel(`chat.${user?.id}`);
      }
    };
  }, [echo, user]);

  const renderItem = ({ item }) => (
    <View
      style={tw(
        `flex flex-row  my-2`,
        item.from == userData.id ? `justify-end` : "justify-start"
      )}
    >
      <View
        style={tw(
          `rounded-lg p-6 max-w-3/4`,
          item.from == userData.id ? "bg-[#1EBEA5]" : "bg-[#8696a0]"
        )}
      >
        <Text style={tw`text-white`}>{item.message}</Text>
      </View>
    </View>
  );

  return (
    <SafeAreaView style={tw`flex-1 bg-[#0b141a]`}>
      <FlatList
        data={messages}
        renderItem={renderItem}
        keyExtractor={(item) => item.id}
        contentContainerStyle={tw`p-4`}
      />
      <KeyboardAvoidingView
        behavior={Platform.OS === "ios" ? "padding" : "height"}
      >
        <View style={tw`p-4 border border-gray-700 gap-2`}>
          <View style={tw`bg-blue-500 p-6 rounded-lg`}>
            <Text style={tw`text-white`}>{userData?.name}</Text>
          </View>
        </View>
        <View style={tw`flex flex-row p-4 border-t border-gray-700 gap-2`}>
          <TextInput
            style={tw`flex-1 bg-gray-800 text-white p-6 rounded-lg`}
            value={inputText}
            onChangeText={setInputText}
            placeholder="Type a message..."
            placeholderTextColor="gray"
          />
          <TouchableOpacity
            onPress={handleSend}
            style={tw`bg-blue-500 p-6 rounded-lg`}
          >
            <Text style={tw`text-white`}>Send</Text>
          </TouchableOpacity>
        </View>
      </KeyboardAvoidingView>
    </SafeAreaView>
  );
};

export default ChatInterface;

2.7 Login users

index.js

import { StatusBar } from "expo-status-bar";
import { Text, TouchableOpacity, View } from "react-native";

import { style as tw } from "twrnc";

import ChatInterface from "../components/chatInterface";
import { useState } from "react";
import { setBearerToken } from "../services/api";
import { Auth } from "../services/auth";

export default function App() {
  const [userData, setUserData] = useState(null);

  async function handleLogin(userCredentials) {
    try {
      const response = await Auth.login(...userCredentials);
      if (response.status == 200) {
        const data = response.data;
        setBearerToken(data.token);
        setUserData(data.user);
      }
    } catch (error) {
      console.error("Error:", error);
    }
  }

  return userData ? (
    <ChatInterface user={userData} />
  ) : (
    <View style={tw(`bg-gray-300 flex-1 items-center justify-center gap-16`)}>
      <TouchableOpacity
        onPress={() =>
          handleLogin(["FIRST-USER-EMAIL-HERE", "FIRST-USER-PASSWORD-HERE"])
        }
        style={tw`w-80 bg-blue-500 p-7 rounded-lg`}
      >
        <Text style={tw`text-white`}>login 1</Text>
      </TouchableOpacity>
      <TouchableOpacity
        onPress={() =>
          handleLogin(["SECOND-USER-EMAIL-HERE", "SECOND-USER-PASSWORD-HERE"])
        }
        style={tw`w-80 bg-blue-500 p-7 rounded-lg`}
      >
        <Text style={tw`text-white`}>login 2</Text>
      </TouchableOpacity>
    </View>
  );
}

3 - Notes

One of the bases was this video on YouTube

Feel free to ask, agree or complain anything

Carregando publicação patrocinada...