Merge pull request #858 from appwrite/react-native

[Do not merge, waiting for pink icon release] Add react-native quick start
This commit is contained in:
Vincent (Wen Yu) Ge
2024-04-08 09:58:48 -04:00
committed by GitHub
20 changed files with 956 additions and 9 deletions

View File

@@ -17,7 +17,7 @@ This tutorial will introduce the following concepts:
2. Authentication
3. Databases and collections
4. Queries and pagination
5. Storage
# Prerequisites {% #prerequisites %}
1. Basic knowledge of Kotlin and Android development.

View File

@@ -0,0 +1,10 @@
<script lang="ts">
import { globToTutorial } from '$lib/utils/tutorials.js';
import { setContext } from 'svelte';
export let data;
const tutorials = globToTutorial(data);
setContext('tutorials', tutorials);
</script>
<slot />

View File

@@ -0,0 +1,11 @@
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = ({ url }) => {
const tutorials = import.meta.glob('./**/*.markdoc', {
eager: true
});
return {
tutorials,
pathname: url.pathname
};
};

View File

@@ -0,0 +1,6 @@
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load: PageLoad = async () => {
redirect(303, '/docs/tutorials/react-native/step-1');
};

View File

@@ -0,0 +1,28 @@
---
layout: tutorial
title: Build an ideas tracker with React Native
description: Learn to build a React Native app with no backend code using an Appwrite backend.
step: 1
difficulty: beginner
readtime: 10
category: Mobile and native
framework: React Native
---
**Idea tracker**: an app to track all the side project ideas that you'll start, but probably never finish.
In this tutorial, you will build Idea tracker with Appwrite and React Native.
# Concepts {% #concepts %}
This tutorial will introduce the following concepts:
1. Setting up your first project
2. Authentication
3. Databases and collections
4. Queries and pagination
# Prerequisites {% #prerequisites %}
1. Android, iOS simulators, or a physical device to run the app
2. Have [Node.js](https://nodejs.org/en) and [NPM](https://www.npmjs.com/) installed on your computer
3. Basic knowledge of React Native and Expo

View File

@@ -0,0 +1,43 @@
---
layout: tutorial
title: Create app
description: Create a React Native app project and integrate with Appwrite.
step: 2
---
# Create React Native project {% #create-react-project %}
Create a React Native app with the `npm create` command.
```sh
npx create-expo-app ideas-tracker
cd ideas-tracker
```
# Add dependencies {% #add-dependencies %}
Install the React Native Appwrite SDK.
```sh
npx expo install react-native-appwrite react-native-url-polyfill
```
Then, install React Navigation to help implement simple navigation logic.
```sh
npm install @react-navigation/native @react-navigation/native-stack
```
Install peer dependencies needed for React Navigation.
```sh
npx expo install react-native-screens react-native-safe-area-context
```
For iOS with bare React Native project, make sure you have CocoaPods installed. Then install the pods to complete the installation:
```
cd ios
pod install
cd ..
```

View File

@@ -0,0 +1,60 @@
---
layout: tutorial
title: Set up Appwrite
description: Import and initialize Appwrite for your React Native application.
step: 3
---
# Create project {% #create-project %}
Head to the [Appwrite Console](https://cloud.appwrite.io/console).
{% only_dark %}
![Create project screen](/images/docs/quick-starts/dark/create-project.png)
{% /only_dark %}
{% only_light %}
![Create project screen](/images/docs/quick-starts/create-project.png)
{% /only_light %}
If this is your first time using Appwrite, create an account and create your first project.
Then, under **Add a platform**, add a **Android** or **Apple** platform with the package/bundle ID `com.example.idea-tracker`.
{% only_dark %}
![Add a platform](/images/docs/quick-starts/dark/add-platform.png)
{% /only_dark %}
{% only_light %}
![Add a platform](/images/docs/quick-starts/add-platform.png)
{% /only_light %}
You can skip optional steps.
# Initialize Appwrite SDK {% #init-sdk %}
To use Appwrite in our React Native app, you'll need to find our project ID.
Find your project's ID in the **Settings** page.
{% only_dark %}
![Project settings screen](/images/docs/quick-starts/dark/project-id.png)
{% /only_dark %}
{% only_light %}
![Project settings screen](/images/docs/quick-starts/project-id.png)
{% /only_light %}
Create a new file `lib/appwrite.js` to hold our Appwrite related code.
Only one instance of the `Client()` class should be created per app.
Add the following code to it, replacing `<YOUR_PROJECT_ID>` with your project ID.
```js
import { Client, Databases, Account } from "react-native-appwrite";
const client = new Client();
client
.setEndpoint("https://cloud.appwrite.io/v1")
.setProject("<YOUR_PROJECT_ID>") // Replace with your project ID
.setPlatform('com.example.idea-tracker');
export const account = new Account(client);
export const databases = new Databases(client);
```

View File

@@ -0,0 +1,166 @@
---
layout: tutorial
title: Add authentication
description: Add authentication to your React Native application.
step: 4
---
# User context {% #user-context %}
In React Native, you can use [context](https://reactjs.org/docs/context.html) to share data between components.
You can use a context and a custom hook to manage our user's data.
Create a new file `contexts/UserContext.jsx` and add the following code to it.
```js
import { ID } from "react-native-appwrite";
import { createContext, useContext, useEffect, useState } from "react";
import { account } from "../lib/appwrite";
import { toast } from "../lib/toast";
const UserContext = createContext();
export function useUser() {
return useContext(UserContext);
}
export function UserProvider(props) {
const [user, setUser] = useState(null);
async function login(email, password) {
const loggedIn = await account.createEmailSession(email, password);
setUser(loggedIn);
toast('Welcome back. You are logged in');
}
async function logout() {
await account.deleteSession("current");
setUser(null);
toast('Logged out');
}
async function register(email, password) {
await account.create(ID.unique(), email, password);
await login(email, password);
toast('Account created');
}
async function init() {
try {
const loggedIn = await account.get();
setUser(loggedIn);
toast('Welcome back. You are logged in');
} catch (err) {
setUser(null);
}
}
useEffect(() => {
init();
}, []);
return (
<UserContext.Provider value={{ current: user, login, logout, register, toast }}>
{props.children}
</UserContext.Provider>
);
}
```
Now, you can use the `useUser` hook to access the user's data from any component wrapped by this context's provider.
# Display toasts {% #display-toasts %}
For a better user experience, display toasts when the users perform an action, such as login, logout, create new ideas, etc.
We can do this by creating a new file `lib/toast.js` and adding the following code to it.
```js
import { ToastAndroid, Platform, AlertIOS } from 'react-native';
export function toast(msg) {
if (Platform.OS === 'android') {
ToastAndroid.show(msg, ToastAndroid.SHORT)
} else {
AlertIOS.alert(msg);
}
}
```
# Login page {% #login-page %}
Create a new file `views/Login.jsx` and add the following code to it.
this page contains a basic form to allow the user to login or register.
Notice how this page consumes the `useUser` hook to access the user's data and perform login and register actions.
```js
import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet } from 'react-native';
import { useUser } from '../contexts/UserContext';
export default function LoginScreen() {
const user = useUser();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
return (
<View style={styles.container}>
<Text style={styles.header}>Login or register</Text>
<TextInput
style={styles.input}
placeholder="Email"
value={email}
onChangeText={setEmail}
/>
<TextInput
style={styles.input}
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<View style={styles.buttonContainer}>
<Button
title="Login"
onPress={
() => {
user.login(email, password)
}
}
/>
<Button
title="Register"
onPress={
() => {
user.register(email, password)
}
}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 16,
},
header: {
fontSize: 24,
marginBottom: 20,
},
input: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
marginBottom: 10,
paddingLeft: 8,
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
},
});
```

View File

@@ -0,0 +1,106 @@
---
layout: tutorial
title: Add routing
description: Add routing to your React Native applicating using Appwrite.
step: 5
---
In this step, you'll add some basic routing to your app.
Based on the user's login status, you'll redirect them to the login page or the home page.
# Home page {% #home-page %}
Create a new file `views/Home.jsx` and add the following stub code to it.
We'll update this page later to display the ideas posted by other users and allow the user to post their ideas.
```js
import React from 'react';
import { View, Text } from 'react-native';
import { useUser } from '../contexts/UserContext';
export default function HomeScreen() {
const user = useUser();
console.log(user.current);
return (
<View>
<Text>
Welcome, {user.current ? user.current.email : 'Please login'}
</Text>
</View>
);
}
```
# Basic routing {% #basic-routing %}
To handle basic routing, you can use the `react-navigation` library.
This router also consumes the `UserContext` to determine if the user is logged in or not to redirect the user automatically.
Create a file `lib/Router.jsx` and add the following code to it.
```js
import { NavigationContainer } from '@react-navigation/native';
import LoginScreen from '../views/Login';
import HomeScreen from '../views/Home';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { useUser } from '../contexts/UserContext';
const Stack = createNativeStackNavigator();
export function Router() {
const user = useUser();
return (
<NavigationContainer>
<Stack.Navigator>
{user.current == null ? (
<Stack.Screen
name="Login"
component={LoginScreen}
options={{ title: 'Login' }}
/>
) : (
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'Home' }}
/>
)}
</Stack.Navigator>
</NavigationContainer>
);
}
```
We'll display this router in the `App.js` file.
```js
import { StyleSheet, Text, View } from 'react-native';
import { UserProvider } from './contexts/UserContext';
import { Router } from './lib/Router';
export default function App() {
return (
<UserProvider>
<Router />
</UserProvider >
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
```
# Test the routing {% #test-routing %}
Now, if you run the app, you should see the login page.
If you create a new account, you should be logged in and redirected to the home page.
Run the app using the following command.
```sh
npm run start
```

View File

@@ -0,0 +1,137 @@
---
layout: tutorial
title: Add database
description: Connect a database to your React Native application using Appwrite Web SDK.
step: 6
---
In this step, you'll set up a database to store ideas in Appwrite, configure permissions, then create a
context to manage ideas in your React Native app.
# Create collection {% #create-collection %}
In Appwrite, data is stored as a collection of documents. Create a collection in the [Appwrite Console](https://cloud.appwrite.io/) to store our ideas.
{% only_dark %}
![Create project screen](/images/docs/tutorials/dark/idea-tracker-collection.png)
{% /only_dark %}
{% only_light %}
![Create project screen](/images/docs/tutorials/idea-tracker-collection.png)
{% /only_light %}
Create a new collection with the following attributes:
| Field | Type | Required |
|-------------|--------|----------|
| userId | String | Yes |
| title | String | Yes |
| description | String | No |
# Configure permissions {% #configure-permissions %}
{% only_dark %}
![Collection permissions screen](/images/docs/tutorials/dark/idea-tracker-permissions.png)
{% /only_dark %}
{% only_light %}
![Collection permissions screen](/images/docs/tutorials/idea-tracker-permissions.png)
{% /only_light %}
Navigate to the **Settings** tab of your collection, add the role **Any** and check the **Read** box.
Next, add a **Users** role and give them access to **Create** by checking those boxes.
These permissions apply to all documents in your new collection.
Finally, enable **Document security** to allow further permissions to be set at the document level.
Remember to click the **Update** button to apply your changes.
# Ideas context {% #ideas-context %}
Now that you have a collection to hold ideas, we can read and write to it from our app.
Like you did with the user data, we will create a React context to hold our ideas.
Create a new file `contexts/IdeasContext.jsx` and add the following code to it.
```js
import { ID, Permission, Role, Query } from "react-native-appwrite";
import { createContext, useContext, useEffect, useState } from "react";
import { databases } from "../lib/appwrite";
import { toast } from "../lib/toast";
export const IDEAS_DATABASE_ID = "default"; // Replace with your database ID
export const IDEAS_COLLECTION_ID = "ideas-tracker"; // Replace with your collection ID
const IdeasContext = createContext();
export function useIdeas() {
return useContext(IdeasContext);
}
export function IdeasProvider(props) {
const [ideas, setIdeas] = useState([]);
async function add(idea) {
const response = await databases.createDocument(
IDEAS_DATABASE_ID,
IDEAS_COLLECTION_ID,
ID.unique(),
idea,
[Permission.write(Role.user(idea.userId))]
);
toast('Ideas added');
setIdeas((ideas) => [response, ...ideas].slice(0, 10));
}
async function remove(id) {
await databases.deleteDocument(IDEAS_DATABASE_ID, IDEAS_COLLECTION_ID, id);
toast('Idea removed');
setIdeas((ideas) => ideas.filter((idea) => idea.$id !== id));
await init(); // Refetch ideas to ensure we have 10 items
}
async function init() {
const response = await databases.listDocuments(
IDEAS_DATABASE_ID,
IDEAS_COLLECTION_ID,
[Query.orderDesc("$createdAt"), Query.limit(10)]
);
setIdeas(response.documents);
}
useEffect(() => {
init();
}, []);
return (
<IdeasContext.Provider value={{ current: ideas, add, remove }}>
{props.children}
</IdeasContext.Provider>
);
}
```
Notice that new ideas have the added permission `Permission.write(Role.user(idea.userId))`.
This permission ensures that only the user who created the idea can modify it.
Remeber to add the `IdeasProvider` to your `App.js` file.
```js
import { StyleSheet, Text, View } from 'react-native';
import { UserProvider } from './contexts/UserContext';
import { IdeasProvider } from './contexts/IdeasContext'; // Add import
import { Router } from './lib/Router';
export default function App() {
return (
<UserProvider>
<!-- Add the Ideas Provider -->
<IdeasProvider>
<Router />
</IdeasProvider>
</UserProvider >
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
```

View File

@@ -0,0 +1,131 @@
---
layout: tutorial
title: Create ideas page
description: Add database queries and pagination using Appwrite in your React Native application.
step: 7
---
Using the `useIdeas` hook you can now display the ideas on the page and create a form to submit new ideas.
If an idea is submitted by the logged-in user, a remove button will be displayed to remove the idea.
While this check uses the user ID to determine the render logic, permissions set in step 6 will be used to enforce
that only the owner of the idea can remove it.
Overwrite the contents of `views/Home.jsx` with the following:
```js
import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet, ScrollView } from 'react-native';
import { useUser } from '../contexts/UserContext';
import { useIdeas } from '../contexts/IdeasContext';
export default function HomeScreen() {
const user = useUser();
const ideas = useIdeas();
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
return (
<ScrollView>
{user.current ? (
<View style={styles.section}>
<Text style={styles.header}>Submit Idea</Text>
<View>
<TextInput
style={styles.input}
placeholder="Title"
value={title}
onChangeText={(text) => setTitle(text)}
/>
<TextInput
style={styles.input}
placeholder="Description"
value={description}
onChangeText={(text) => setDescription(text)}
/>
<Button
title="Submit"
onPress={() =>
ideas.add({ userId: user.current.$id, title, description })
}
/>
</View>
</View>
) : (
<View style={styles.section}>
<Text>Please login to submit an idea.</Text>
</View>
)}
<View style={styles.section}>
<Text style={styles.header}>Latest Ideas</Text>
<View>
{ideas.current.map((idea) => (
<View key={idea.$id} style={styles.card}>
<Text style={styles.cardTitle}>{idea.title}</Text>
<Text style={styles.cardDescription}>{idea.description}</Text>
{/* Show the remove button to idea owner. */}
{user.current && user.current.$id === idea.userId && (
<Button
title="Remove"
onPress={() => ideas.remove(idea.$id)}
/>
)}
</View>
))}
</View>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
section: {
marginBottom: 20,
padding: 16,
},
container: {
flex: 1,
justifyContent: 'center',
padding: 16,
},
header: {
fontSize: 24,
marginBottom: 20,
},
input: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
marginBottom: 10,
paddingLeft: 8,
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
},
card: {
backgroundColor: 'white',
padding: 16,
marginBottom: 16,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
cardTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 8,
},
cardDescription: {
fontSize: 14,
marginBottom: 8,
},
});
```

View File

@@ -0,0 +1,14 @@
---
layout: tutorial
title: Next steps
description: Run your React Native project built with Appwrite
step: 8
---
# Test your project {% #test-project %}
You can run your projects with `npm run start`. This will start the Metro bundler and open the Expo Go app on your device.
You can also run your project on an emulator or simulator by pressing `i` for iOS and `a` for Android.
# Bundling for production {% #bundle-production %}
Appwrite's React Native SDK is designed to work with the Expo Metro bundler.
When you are ready to build your app for production, you can learn more in the [Expo documentation](https://docs.expo.dev/).

View File

@@ -27,7 +27,7 @@ This tutorial will introduce the following concepts:
2. Authentication
3. Databases and collections
4. Queries and pagination
5. Storage
# Prerequisites {% #prerequisites %}

View File

@@ -50,7 +50,7 @@ export function IdeasProvider(props) {
ID.unique(),
idea
);
setIdeas((ideas) => [response.$id, ...ideas].slice(0, 10));
setIdeas((ideas) => [response, ...ideas].slice(0, 10));
}
async function remove(id) {

View File

@@ -29,7 +29,7 @@ This tutorial will introduce the following concepts:
2. Authentication
3. Databases and collections
4. Queries and pagination
5. Storage
# Prerequisites {% #prerequisites %}

View File

@@ -26,7 +26,7 @@ This tutorial will introduce the following concepts:
2. Authentication
3. Databases and collections
4. Queries and pagination
5. Storage
# Prerequisites {% #prerequisites %}
1. Basic knowledge of JavaScript and Svelte.

View File

@@ -26,7 +26,7 @@ This tutorial will introduce the following concepts:
2. Authentication
3. Databases and collections
4. Queries and pagination
5. Storage
# Prerequisites {% #prerequisites %}