Hướng dẫn cài đặt và cấu hình Supabase trên React Native
Trong bài viết này, chúng ta sẽ cùng tìm hiểu cách tích hợp Supabase – một nền tảng backend mã nguồn mở mạnh mẽ – vào dự án React Native. Supabase cung cấp các tính năng như xác thực người dùng, cơ sở dữ liệu, realtime, và lưu trữ file cực kỳ tiện lợi. Bài viết này phù hợp với những bạn mới bắt đầu và muốn tìm hiểu cách kết nối Supabase vào ứng dụng React Native của mình.
1. Tạo file cấu hình supabase.ts
Đây là nơi bạn khởi tạo kết nối đến Supabase
. Hãy tạo file supabase.ts
trong thư mục lib
hoặc utils
của bạn
import { AppState } from "react-native";
import "react-native-get-random-values";
import * as aesjs from "aes-js";
import * as SecureStore from "expo-secure-store";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL as string;
const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY as string;
class LargeSecureStore {
private async _encrypt(key: string, value: string) {
const encryptionKey = crypto.getRandomValues(new Uint8Array(256 / 8));
const cipher = new aesjs.ModeOfOperation.ctr(
encryptionKey,
new aesjs.Counter(1),
);
const encryptedBytes = cipher.encrypt(aesjs.utils.utf8.toBytes(value));
await SecureStore.setItemAsync(
key,
aesjs.utils.hex.fromBytes(encryptionKey),
);
return aesjs.utils.hex.fromBytes(encryptedBytes);
}
private async _decrypt(key: string, value: string) {
const encryptionKeyHex = await SecureStore.getItemAsync(key);
if (!encryptionKeyHex) {
return encryptionKeyHex;
}
const cipher = new aesjs.ModeOfOperation.ctr(
aesjs.utils.hex.toBytes(encryptionKeyHex),
new aesjs.Counter(1),
);
const decryptedBytes = cipher.decrypt(aesjs.utils.hex.toBytes(value));
return aesjs.utils.utf8.fromBytes(decryptedBytes);
}
async getItem(key: string) {
const encrypted = await AsyncStorage.getItem(key);
if (!encrypted) {
return encrypted;
}
return await this._decrypt(key, encrypted);
}
async removeItem(key: string) {
await AsyncStorage.removeItem(key);
await SecureStore.deleteItemAsync(key);
}
async setItem(key: string, value: string) {
const encrypted = await this._encrypt(key, value);
await AsyncStorage.setItem(key, encrypted);
}
}
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
storage: new LargeSecureStore(),
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
},
});
AppState.addEventListener("change", (state) => {
if (state === "active") {
supabase.auth.startAutoRefresh();
} else {
supabase.auth.stopAutoRefresh();
}
});
Lưu ý: Thay supabaseUrl
và supabaseAnonKey
bằng thông tin thật từ dự án của bạn trên Supabase.
2. Tạo file supabase-provider.tsx
Đây là nơi tạo context để bạn có thể dễ dàng sử dụng Supabase ở bất kỳ component nào.
import {
createContext,
PropsWithChildren,
useContext,
useEffect,
useState,
} from "react";
import { SplashScreen, useRouter } from "expo-router";
import { Session } from "@supabase/supabase-js";
import { supabase } from "@/config/supabase";
SplashScreen.preventAutoHideAsync();
type AuthState = {
initialized: boolean;
session: Session | null;
signUp: (email: string, password: string) => Promise<void>;
signIn: (email: string, password: string) => Promise<void>;
signOut: () => Promise<void>;
};
export const AuthContext = createContext<AuthState>({
initialized: false,
session: null,
signUp: async () => {},
signIn: async () => {},
signOut: async () => {},
});
export const useAuth = () => useContext(AuthContext);
export function AuthProvider({ children }: PropsWithChildren) {
const [initialized, setInitialized] = useState(false);
const [session, setSession] = useState<Session | null>(null);
const router = useRouter();
const signUp = async (email: string, password: string) => {
const { data, error } = await supabase.auth.signUp({
email,
password,
});
if (error) {
console.error("Error signing up:", error);
return;
}
if (data.session) {
setSession(data.session);
console.log("User signed up:", data.user);
} else {
console.log("No user returned from sign up");
}
};
const signIn = async (email: string, password: string) => {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
console.error("Error signing in:", error);
return;
}
if (data.session) {
setSession(data.session);
console.log("User signed in:", data.user);
} else {
console.log("No user returned from sign in");
}
};
const signOut = async () => {
const { error } = await supabase.auth.signOut();
if (error) {
console.error("Error signing out:", error);
return;
} else {
console.log("User signed out");
}
};
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
});
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
setInitialized(true);
}, []);
useEffect(() => {
if (initialized) {
SplashScreen.hideAsync();
if (session) {
router.replace("/explore");
} else {
router.replace("/welcome");
}
}
// eslint-disable-next-line
}, [initialized, session]);
return (
<AuthContext.Provider
value={{
initialized,
session,
signUp,
signIn,
signOut,
}}
>
{children}
</AuthContext.Provider>
);
}
3. Bọc Provider ngoài cùng trong _layout.tsx
Bạn cần bọc toàn bộ ứng dụng trong Provider để mọi nơi trong app đều có thể sử dụng được supabase
.
import "../global.css";
import { Stack } from "expo-router";
import { AuthProvider } from "@/context/supabase-provider";
import { useColorScheme } from "@/lib/useColorScheme";
import { colors } from "@/constants/colors";
export default function AppLayout() {
const { colorScheme } = useColorScheme();
return (
<AuthProvider>
<Stack screenOptions={{ headerShown: false, gestureEnabled: false }}>
<Stack.Screen
name="sign-up"
options={{
presentation: "modal",
headerShown: true,
headerTitle: "Sign Up",
headerStyle: {
backgroundColor:
colorScheme === "dark"
? colors.dark.background
: colors.light.background,
},
headerTintColor:
colorScheme === "dark"
? colors.dark.foreground
: colors.light.foreground,
gestureEnabled: true,
}}
/>
<Stack.Screen
name="sign-in"
options={{
presentation: "modal",
headerShown: true,
headerTitle: "Sign In",
headerStyle: {
backgroundColor:
colorScheme === "dark"
? colors.dark.background
: colors.light.background,
},
headerTintColor:
colorScheme === "dark"
? colors.dark.foreground
: colors.light.foreground,
gestureEnabled: true,
}}
/>
</Stack>
</AuthProvider>
);
}
4. Cách sử dụng Supabase trong component
Sau khi cấu hình xong, bạn có thể sử dụng Supabase ở bất kỳ component nào như sau:
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { ActivityIndicator, View } from "react-native";
import * as z from "zod";
import { SafeAreaView } from "@/libUI/safe-area-view";
import { Button } from "@/libUI/button";
import { Form, FormField, FormInput } from "@/libUI/form";
import { Text } from "@/libUI/text";
import { H1 } from "@/libUI/typography";
import { useAuth } from "@/context/supabase-provider";
const formSchema = z.object({
email: z.string().email("Please enter a valid email address."),
password: z
.string()
.min(8, "Please enter at least 8 characters.")
.max(64, "Please enter fewer than 64 characters."),
});
export default function SignIn() {
const { signIn } = useAuth();
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: "",
password: "",
},
});
async function onSubmit(data: z.infer<typeof formSchema>) {
try {
await signIn(data.email, data.password);
form.reset();
} catch (error: Error | any) {
console.error(error.message);
}
}
return (
<SafeAreaView className="flex-1 bg-background p-4" edges={["bottom"]}>
<View className="flex-1 gap-4 web:m-4">
<H1 className="self-start ">Sign In</H1>
<Form {...form}>
<View className="gap-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormInput
label="Email"
placeholder="Email"
autoCapitalize="none"
autoComplete="email"
autoCorrect={false}
keyboardType="email-address"
{...field}
/>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormInput
label="Password"
placeholder="Password"
autoCapitalize="none"
autoCorrect={false}
secureTextEntry
{...field}
/>
)}
/>
</View>
</Form>
</View>
<Button
size="default"
variant="default"
onPress={form.handleSubmit(onSubmit)}
disabled={form.formState.isSubmitting}
className="web:m-4"
>
{form.formState.isSubmitting ? (
<ActivityIndicator size="small" />
) : (
<Text>Sign In</Text>
)}
</Button>
</SafeAreaView>
);
}
Kết luận
Việc tích hợp Supabase vào React Native không quá phức tạp nếu bạn làm theo từng bước một cách cẩn thận. Supabase là một lựa chọn tuyệt vời cho các dự án mobile nhờ khả năng realtime, xác thực người dùng dễ dàng và quản lý dữ liệu hiệu quả. Với các bước trên, bạn đã có một nền tảng vững chắc để tiếp tục phát triển ứng dụng của mình với Supabase.
Chúc bạn thành công và đừng ngần ngại khám phá thêm các tính năng nâng cao như Auth, Storage, hoặc Realtime của Supabase trong tương lai!