Sign in with google React native Expo

Đang làm chức năng sign-in với google trên react native mà rối não quá.
Cụ thể mình dùng expo-auth-session. Đây là component của mình

import * as React from "react";
import { Image } from "expo-image";
import { StyleSheet } from "react-native";
import { useAppSelector, useAppDispatch } from "../../hooks";
import * as SecureStore from "expo-secure-store";
import {
  Checkbox,
  Box,
  Text,
  Heading,
  VStack,
  FormControl,
  Input,
  Link,
  Button,
  HStack,
  Center,
} from "native-base";
import { useForm, Controller, SubmitHandler } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { ILoginRequest, ILoginResponse } from "../../types";
import * as yup from "yup";
import { LoginScreenProps } from "../../types";
import { RootState, login } from "../../store";
import { authApi } from "../../services/auth.services";
import * as WebBrowser from "expo-web-browser";
import * as Google from "expo-auth-session/providers/google";
WebBrowser.maybeCompleteAuthSession();
const schema: yup.ObjectSchema<ILoginRequest> = yup
  .object({
    email: yup
      .string()
      .required("Email không được để trống")
      .email("Email không hợp lệ"),
    password: yup
      .string()
      .min(8, "Mật khẩu phải có ít nhất 8 kí tự")
      .required("password required"),
  })
  .required();
const Login: React.FC<LoginScreenProps> = ({
  navigation,
  route,
}: LoginScreenProps) => {
  const [isChecked, setIsChecked] = React.useState(false);
  const [token, setLoginToken] = React.useState("");
  const [userInfo, setUserInfo] = React.useState(null);
  const {
    control,
    handleSubmit,
    formState: { errors },
  } = useForm<ILoginRequest>({
    defaultValues: {
      email: "",
      password: "",
    },
    resolver: yupResolver(schema),
  });
  const [request, response , promptAsync] = Google.useAuthRequest({
    androidClientId: "632434206355-XXXXXXX",
  });
  const { setToken } = route.params;
  const dispatch = useAppDispatch();
  const onSubmit: SubmitHandler<ILoginRequest> = async (
    data: ILoginRequest
  ) => {
    // call api...
    // After call api: assume the API give token, we need to set token
    await authApi
      .login(data)
      .then(async (res) => {
        if (res.data?.data) {
          dispatch(login(res.data?.data.user));
          await SecureStore.setItemAsync("token", res.data?.data.token);
          await SecureStore.setItemAsync(
            "user",
            JSON.stringify(res.data?.data.user)
          );
          setToken(res.data.data.token);
        }
      })
      .catch((error) => {
        // Print error to the screen
        console.log(error.res.data);
      });
  };
  React.useEffect(() => {
    handleEffect();
  }, [response , token]);
  async function handleEffect() {
    const user = await getLocalUser();
    console.log("user", user);
    if (!user) {
      console.log('in here');
      if (response ?.type === "success") {
        if (response .authentication?.accessToken)
        {
          setToken(response .authentication?.accessToken)
          getUserInfo(response .authentication?.accessToken);
          setLoginToken(response .authentication?.accessToken)
        }
        else
          console.log("Khong nhan duoc accessToken tu google");
      }
      else
      console.log(response )
    } else {
      setUserInfo(user);
      console.log("loaded locally");
    }
  }
  const getLocalUser = async () => {
    const data = await SecureStore.getItemAsync("user");
    if (!data) return null;
    return JSON.parse(data);
  };
  const getUserInfo = async (token:string) => {
    if (!token) return;
    try {
      console.log('acess token cuả google response: ', token);
      const res = await fetch(
        "https://www.googleapis.com/userinfo/v2/me",
        {
          headers: { Authorization: `Bearer ${token}` },
        }
      );
      const user = await res.json();
      await SecureStore.setItemAsync("user", JSON.stringify(user));
      dispatch(login(user));
      setUserInfo(user);
    } catch (error) {
      // Add your own error handler here
    }
  };
  return (
    <Center flex="1" px="3">
      <Center w="100%">
        <Box safeArea p="2" py="8" w="100%" /* maxW="500" */>
          <Heading
            size="xl"
            fontWeight="bold"
            color="coolGray.800"
            _dark={{
              color: "warmGray.50",
            }}
          >
            Đăng nhập
          </Heading>
          <Heading
            mt="1"
            _dark={{
              color: "warmGray.200",
            }}
            color="coolGray.600"
            fontWeight="medium"
            size="xs"
          >
            <HStack mt="6" justifyContent="center">
              <Text
                fontSize="18"
                color="black"
                _dark={{
                  color: "warmGray.200",
                }}
              >
                hoặc{" "}
              </Text>
              <Link
                _text={{
                  color: "#1890ff",
                  fontWeight: "medium",
                  fontSize: "18",
                }}
                href="#"
              >
                Tạo mới tài khoản
              </Link>
            </HStack>
          </Heading>
          <VStack space={3} mt="5">
            <FormControl /* isInvalid={!!errors.email} */>
              <Controller
                control={control}
                rules={
                  {
                    // required: true,
                  }
                }
                render={({ field: { onChange, onBlur, value } }) => (
                  <Input
                    placeholder="Email"
                    onBlur={onBlur}
                    onChangeText={onChange}
                    value={value}
                  />
                )}
                name="email"
              />
              {errors.email ? (
                <Text style={{ color: "red" }}>{errors.email.message}</Text>
              ) : null}
            </FormControl>
            <FormControl>
              <Controller
                control={control}
                rules={{
                  maxLength: 100,
                }}
                render={({ field: { onChange, onBlur, value } }) => (
                  <Input
                    placeholder="Password"
                    onBlur={onBlur}
                    onChangeText={onChange}
                    value={value}
                    secureTextEntry
                  />
                )}
                name="password"
              />
              {errors.password ? (
                <Text style={{ color: "red" }}>{errors.password.message}</Text>
              ) : null}
            </FormControl>
            <Checkbox
              value="one"
              isChecked={isChecked}
              onChange={() => setIsChecked(!isChecked)}
              my={2}
              colorScheme="blue"
            >
              Ghi nhớ thông tin đăng nhập
            </Checkbox>
            <Button
              mt="2"
              colorScheme="indigo"
              style={styles.buttonStyle}
              onPress={handleSubmit(onSubmit)}
            >
              <Text ml={2} fontWeight="medium" style={{ color: "white" }}>
                Đăng nhập
              </Text>
            </Button>
            <Button
              mt="2"
              justifyContent="center"
              alignItems="center"
              bg="white" // Set the background color to white
              borderWidth={1} // Set the border width
              borderColor="gray.500" // Set the border color to gray
              borderRadius="md" // Set the border radius
              p={2} // Add padding to the button
              onPress={() => {
                promptAsync();
              }}
            >
              <HStack space={2} alignItems="center">
                <Image
                  style={styles.logoIcon}
                  contentFit="cover"
                  source={require("../../assets/logo.png")}
                />
                <Text ml={2} fontWeight="regular">
                  Đăng nhập với Google
                </Text>
              </HStack>
            </Button>
          </VStack>
          <Link
            _text={{
              fontSize: "14",
              fontWeight: "500",
              color: "#1890ff",
            }}
            alignSelf="center"
            mt="3"
          >
            Quên mật khẩu?
          </Link>
        </Box>
        <HStack space={1} alignItems="center" justifyContent="center">
          <Text>Chưa có tài khoản?</Text>
          <Link
            isUnderlined={false}
            _text={{
              _light: {
                color: "info.600",
              },
            }}
            onPress={() =>
              navigation.navigate("Register", { setToken: setToken })
            }
          >
            Đăng ký
          </Link>
        </HStack>
      </Center>
    </Center>
  );
};
const styles = StyleSheet.create({
  logoIcon: {
    width: 20,
    height: 20,
    marginLeft: 10,
  },
  buttonStyle: {
    backgroundColor: "#1890ff",
    fontWeight: "bold",
    color: "white",
  },
});
export default Login;

Khoan bàn tới chuyện code ẹ cái đã, mình đã remote build thử tạo ra eas development, đại khái là tạo ra một file .apk trên expo, mình dùng điện thoại(android) tải file đó về và chạy, nó là expo-dev-client.
Thì vấn đề ở đây đó là sau khi mà nhấn vào nút đăng nhập với gg, thì chọn tài khoản xong nó vào trang của google luôn và không quay trở lại app nữa. Đóng màn hình google, quay trở lại app thì response của Google.useAuthRequest trả về {type: dismiss}.
Chú thích là trong app.json của mình đã có “scheme”: “…” rồi.
Mọi người có ai từng gặp lỗi giống mình thì có thể đưa ra giải pháp ạ

1 Like

bạn cần xử lý callback khi quá trình xác thực với Google được hoàn thành :))) .

  1. Khi người dùng nhấn nút “Đăng nhập với Google”, bạn gọi promptAsync() để bắt đầu quá trình xác thực:
const [request, response, promptAsync] = Google.useAuthRequest({
  androidClientId: /* your client id */  
});

// Handle button press
const loginWithGoogle = async () => {
  await promptAsync();
}
  1. Định nghĩa callback để xử lý khi quá trình xác thực hoàn tất:
useEffect(() => {
  if (response?.type === 'success') {
    // Handle success case
  } else if (response?.type === 'cancel') {
    // Handle cancel case 
  }
}, [response]);
  1. Trong phần xử lý success case, lấy token từ response và call API lấy thông tin người dùng:
if (response?.type === 'success') {

  const {authentication} = response;

  // Call API
  getUserInfo(authentication.accessToken)
    .then(user => {
      // Save user & token
    });

}

Kiểm tra thiết lập Redirect URI:

Trước hết, hãy đảm bảo bạn đã cấu hình redirect URI của ứng dụng của mình trên Google Developer Console để Google biết khi xác thực xong nó sẽ quay trở lại đúng địa chỉ URI của ứng dụng của bạn.
Bạn cần đảm bảo URI này phải khớp với thông số đã thiết lập trên Google Developer Console và trong mã nguồn app.json.
Xác thực lại thông số của Google.useAuthRequest:

Đảm bảo rằng bạn đã cung cấp thông tin redirectUri trong useAuthRequest để đảm bảo rằng quá trình xác thực sẽ chuyển hướng trở lại ứng dụng của bạn sau khi hoàn thành.
Xử lý quay trở lại sau khi xác thực:

Mô hình xác thực với Google trên React Native thường đòi hỏi việc xử lý sự kiện khi quá trình xác thực hoàn thành. Bạn cần đảm bảo rằng sau khi xác thực thành công, mã nguồn của bạn phải xử lý sự kiện để quay trở lại ứng dụng và tiếp tục quá trình đăng nhập.
Theo dõi log và Debug:

Thực hiện theo dõi log và debug để xác định rõ hơn vấn đề xảy ra sau khi xác thực với Google. Điều này sẽ giúp bạn phát hiện vấn đề cụ thể đang xảy ra.

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?