Skip to content

Commit 661a55d

Browse files
committed
Initialize Expo to-do-app with SQLite CloudSync integration
1 parent bf9f2b5 commit 661a55d

22 files changed

+1479
-0
lines changed

examples/to-do-app/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copy from the SQLite Cloud Dashboard
2+
# eg: sqlitecloud://myhost.cloud:8860/my-remote-database.sqlite?apikey=myapikey
3+
CONNECTION_STRING = "<your-connection-string>"

examples/to-do-app/.gitignore

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# dependencies
2+
node_modules/
3+
4+
# Expo
5+
.expo/
6+
dist/
7+
web-build/
8+
9+
# Native
10+
*.orig.*
11+
*.jks
12+
*.p8
13+
*.p12
14+
*.key
15+
*.mobileprovision
16+
17+
# Metro
18+
.metro-health-check*
19+
20+
# debug
21+
npm-debug.*
22+
yarn-debug.*
23+
yarn-error.*
24+
25+
# macOS
26+
.DS_Store
27+
*.pem
28+
29+
# local env files
30+
.env*.local
31+
32+
# typescript
33+
*.tsbuildinfo
34+
35+
.env
36+
37+
/android
38+
/ios

examples/to-do-app/App.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { PaperProvider } from "react-native-paper";
2+
import { NavigationContainer } from "@react-navigation/native";
3+
import { createStackNavigator } from "@react-navigation/stack";
4+
import Cover from "./screens/Cover";
5+
import Home from "./screens/Home";
6+
import AddTaskModal from "./components/AddTaskModal";
7+
import { GestureHandlerRootView } from "react-native-gesture-handler";
8+
import Categories from './screens/Categories';
9+
import { SyncProvider } from './components/SyncContext';
10+
11+
export default function App() {
12+
const Stack = createStackNavigator();
13+
14+
return (
15+
<GestureHandlerRootView style={{ flex: 1 }}>
16+
<SyncProvider>
17+
<NavigationContainer>
18+
<PaperProvider>
19+
<Stack.Navigator>
20+
<Stack.Screen name="Cover" component={Cover} />
21+
<Stack.Screen name="Categories" component={Categories} />
22+
<Stack.Screen name="Tasks" component={Home} />
23+
<Stack.Group screenOptions={{ presentation: 'modal' }}>
24+
<Stack.Screen name="Add Task" component={AddTaskModal} />
25+
</Stack.Group>
26+
</Stack.Navigator>
27+
</PaperProvider>
28+
</NavigationContainer>
29+
</SyncProvider>
30+
</GestureHandlerRootView>
31+
);
32+
}

examples/to-do-app/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Expo CloudSync Example
2+
3+
A simple Expo example demonstrating SQLite synchronization with CloudSync. Build cross-platform apps that sync data seamlessly across devices.
4+
5+
## 🚀 Quick Start
6+
7+
### 1. Clone the template
8+
9+
Create a new project using this template:
10+
```bash
11+
npx create-expo-app MyApp --template @sqliteai/todoapp
12+
cd MyApp
13+
npm install
14+
```
15+
16+
### 2. Database Setup
17+
18+
1. Create database in [SQLite Cloud Dashboard](https://dashboard.sqlitecloud.io/).
19+
2. Execute the exact schema from `to-do-app.sql`.
20+
3. Enable OffSync for all tables on the remote database from the **SQLite Cloud Dashboard -> Databases**.
21+
22+
### 3. Environment Configuration
23+
24+
Rename the `.env.example` into `.env` and fill with your values.
25+
26+
> **⚠️ SECURITY WARNING**: This example puts database connection strings directly in `.env` files for demonstration purposes only. **Do not use this pattern in production.**
27+
>
28+
> **Why this is unsafe:**
29+
> - Connection strings contain sensitive credentials
30+
> - Client-side apps expose all environment variables to users
31+
> - Anyone can inspect your app and extract database credentials
32+
>
33+
> **For production apps:**
34+
> - Use the secure [sport-tracker-app](https://github.com/sqliteai/sqlite-sync/tree/main/examples/sport-tracker-app) pattern with authentication tokens and row-level security
35+
> - Never embed database credentials in client applications
36+
37+
### 4. Build and run the App
38+
39+
```bash
40+
npx expo prebuild # run once
41+
npm start
42+
```
43+
44+
## ✨ Features
45+
46+
- **Add Tasks** - Create new tasks with titles and optional tags.
47+
- **Edit Task Status** - Update task status when completed.
48+
- **Delete Tasks** - Remove tasks from your list.
49+
- **Dropdown Menu** - Select categories for tasks from a predefined list.
50+
- **Cross-Platform** - Works on iOS and Android
51+
- **Offline Support** - Works offline, syncs when connection returns
52+

examples/to-do-app/app.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"expo": {
3+
"name": "todoapp",
4+
"slug": "todoapp",
5+
"version": "1.0.0",
6+
"orientation": "portrait",
7+
"icon": "./assets/icon.png",
8+
"userInterfaceStyle": "light",
9+
"description": "A todo app demonstrating SQLite CloudSync extension functionalities",
10+
"keywords": ["sqlite", "cloudsync", "todo", "sync"],
11+
"privacy": "public",
12+
"platforms": ["ios", "android"],
13+
"plugins": [
14+
"./plugins/CloudSyncSetup.js"
15+
],
16+
"splash": {
17+
"image": "./assets/splash.png",
18+
"resizeMode": "contain",
19+
"backgroundColor": "#ffffff"
20+
},
21+
"ios": {
22+
"supportsTablet": true,
23+
"bundleIdentifier": "ai.sqlite.todoapp"
24+
},
25+
"android": {
26+
"adaptiveIcon": {
27+
"foregroundImage": "./assets/adaptive-icon.png",
28+
"backgroundColor": "#ffffff"
29+
},
30+
"package": "ai.sqlite.todoapp"
31+
},
32+
"extra": {
33+
"eas": {
34+
"projectId": "faffd54b-fc15-4368-987a-a73b08640cfd"
35+
}
36+
},
37+
"owner": "sqliteai"
38+
}
39+
}
17.1 KB
Loading

examples/to-do-app/assets/icon.png

21.9 KB
Loading
46.2 KB
Loading

examples/to-do-app/babel.config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module.exports = function (api) {
2+
api.cache(false);
3+
return {
4+
presets: ["babel-preset-expo"],
5+
plugins: [
6+
"react-native-paper/babel",
7+
[
8+
"module:react-native-dotenv",
9+
{
10+
moduleName: "@env",
11+
path: ".env",
12+
},
13+
],
14+
],
15+
};
16+
};
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { useState, useEffect } from "react";
2+
import { View, StyleSheet, Alert } from "react-native";
3+
import { TextInput, Button, Modal } from "react-native-paper";
4+
import DropdownMenu from "./DropdownMenu";
5+
import { db } from "../db/dbConnection";
6+
7+
export default AddTaskModal = ({
8+
modalVisible,
9+
addTaskTag,
10+
setModalVisible,
11+
}) => {
12+
const [taskTitle, setTaskTitle] = useState("");
13+
const [tagsList, setTagsList] = useState([]);
14+
const [selectedTag, setSelectedTag] = useState({});
15+
16+
const closeModal = () => {
17+
setModalVisible(false);
18+
};
19+
20+
const handleAddTask = () => {
21+
if (taskTitle.trim()) {
22+
addTaskTag({ title: taskTitle.trim(), isCompleted: false }, selectedTag);
23+
setTaskTitle("");
24+
setSelectedTag({});
25+
closeModal();
26+
} else {
27+
Alert.alert("Please add a new task.");
28+
}
29+
};
30+
31+
const getTags = async () => {
32+
try {
33+
const tags = await db.execute("SELECT * FROM tags");
34+
setTagsList(tags.rows);
35+
} catch (error) {
36+
console.error("Error getting tags", error);
37+
}
38+
};
39+
40+
useEffect(() => {
41+
getTags();
42+
}, []);
43+
44+
return (
45+
<Modal
46+
style={styles.modalContainer}
47+
visible={modalVisible}
48+
onDismiss={closeModal}
49+
>
50+
<View>
51+
<View style={styles.newTaskBox}>
52+
<TextInput
53+
mode="flat"
54+
style={[
55+
styles.textInput
56+
]}
57+
contentStyle={styles.textInputContent}
58+
underlineColor="transparent"
59+
activeUnderlineColor="#6BA2EA"
60+
value={taskTitle}
61+
onChangeText={setTaskTitle}
62+
label="Enter a new task"
63+
keyboardType="default"
64+
/>
65+
</View>
66+
<DropdownMenu tagsList={tagsList} setSelectedTag={setSelectedTag} />
67+
<Button
68+
style={styles.addTaskButton}
69+
textColor="black"
70+
onPress={handleAddTask}
71+
>
72+
Add task
73+
</Button>
74+
</View>
75+
</Modal>
76+
);
77+
};
78+
79+
const styles = StyleSheet.create({
80+
modalContainer: {
81+
flex: 1,
82+
backgroundColor: "white",
83+
padding: 10,
84+
},
85+
newTaskBox: {
86+
flexDirection: "row",
87+
alignItems: "center",
88+
justifyContent: "center",
89+
borderWidth: 1,
90+
borderColor: "lightgray",
91+
backgroundColor: "#f0f5fd",
92+
marginBottom: 10,
93+
},
94+
textInput: {
95+
width: "100%",
96+
backgroundColor: "transparent",
97+
height: 50,
98+
},
99+
textInputContent: {
100+
backgroundColor: "transparennt",
101+
borderWidth: 0,
102+
paddingLeft: 10,
103+
},
104+
button: {
105+
height: 50,
106+
width: 50,
107+
justifyContent: "center",
108+
alignItems: "center",
109+
},
110+
closeButton: {
111+
alignItems: "flex-start",
112+
bottom: 180,
113+
left: -10,
114+
zIndex: 1,
115+
},
116+
addTaskButton: {
117+
backgroundColor: "#b2cae9",
118+
marginTop: 10,
119+
},
120+
});

0 commit comments

Comments
 (0)