Merge branch 'main' into feat-improve-code-fence

This commit is contained in:
Vincent (Wen Yu) Ge
2024-04-08 19:24:46 -04:00
142 changed files with 6598 additions and 501 deletions

View File

@@ -12,6 +12,7 @@
type MappedTutorial = (typeof data.tutorials)[number];
const iconMap: Record<string, string> = {
'react native': 'icon-react',
react: 'icon-react',
vue: 'web-icon-vue',
angular: 'icon-angular',

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

@@ -120,10 +120,10 @@ To catch that moment in time when the page loads, we call our `fetch` function,
Second, it's likely that a user will want to delete one of their ideas from the database.
We help them do that by adding a delete button in the top right corner of the idea list item, but only on the ideas added by the user itself.
Add the file `IdeasList` from the `componenents` directory and insert the following code:
Add the file `IdeasList` from the `components` directory and insert the following code:
```vue
<!-- componenents/IdeasList.vue -->
<!-- components/IdeasList.vue -->
<script setup>
import { onMounted } from 'vue';

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

@@ -9,7 +9,7 @@ In our app we want to have a navigation bar that is always visible. We will add
- a logout button if the user is logged in.
- a login button if the user is not logged in.
Update the App componenent in `src/App.jsx`:
Update the App component in `src/App.jsx`:
```client-web
import { Login } from "./pages/Login";

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

@@ -16,7 +16,7 @@ import { getIdeas } from '$lib/ideas';
export async function load() {
return {
ideas: getIdeas()
ideas: await getIdeas()
};
}
```
@@ -118,4 +118,4 @@ Simple as that! Now, let's create the page itself. Replace the contents in `src/
</style>
```
With this you have successfully created an Ideas Tracker! You can now submit ideas and view them.
With this you have successfully created an Ideas Tracker! You can now submit ideas and view them.

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 %}

View File

@@ -9,7 +9,7 @@ In our app we want to have a navigation bar that is always visible. Use the `use
- a logout button if the user is logged in.
- a login button if the user is not logged in.
Update the App componenent in `src/App.vue`:
Update the App component in `src/App.vue`:
```vue
<script setup>