Skip to content

Commit 9842df6

Browse files
Django RN demo app: fix some issues picked up during Django backend cleanup (#217)
Co-authored-by: Steven Ontong <[email protected]>
1 parent f58c287 commit 9842df6

29 files changed

+393
-623
lines changed

.changeset/eighty-pots-search.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'django-react-native-todolist': minor
3+
---
4+
5+
Use react hooks. Remove Mobx stores.

demos/django-react-native-todolist/README.md

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,31 @@ Run on Android
2525
pnpm android
2626
```
2727

28-
## Set up Django Backend
28+
## Service Configuration
29+
30+
This demo can be used with cloud or local services.
31+
32+
### Local Services
33+
34+
The [Self Hosting Demo](https://github.com/powersync-ja/self-host-demo) repository contains a Docker Compose Django backend demo which can be used with this client.
35+
See [instructions](https://github.com/powersync-ja/self-host-demo/blob/feature/django-backend/demos/django/README.md) for starting the backend locally.
36+
37+
#### Android
38+
39+
Note that Android requires port forwarding of local services. These can be configured with ADB as below:
40+
41+
```bash
42+
adb reverse tcp:8080 tcp:8080 && adb reverse tcp:6061 tcp:6061
43+
```
44+
45+
### Cloud Services
46+
47+
#### Set up Django Backend
2948

3049
This demo requires that you have the [PowerSync Django Backend: Todo List Demo](https://github.com/powersync-ja/powersync-django-backend-todolist-demo) running on your machine.
3150
Follow the guide in the README of the PowerSync Django Backend to set it up.
3251

33-
## Set up PowerSync Instance
52+
#### Set up PowerSync Instance
3453

3554
Create a new PowerSync instance, connecting to the database of the Supabase project.
3655

@@ -42,11 +61,11 @@ bucket_definitions:
4261
# Separate bucket per todo list
4362
parameters: select id as list_id from lists where owner_id = token_parameters.user_id
4463
data:
45-
- select * from api_list
46-
- select * from api_todo
64+
- select * from lists
65+
- select * from todos
4766
```
4867
49-
## Configure The App
68+
#### Configure The App
5069
5170
Copy the `AppConfig.template.ts` to a usable file
5271

demos/django-react-native-todolist/app/_layout.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Stack } from 'expo-router';
2-
import React from 'react';
2+
import { PowerSyncContext } from '@powersync/react';
3+
import { useSystem } from '../library/stores/system';
34

45
/**
56
* This App uses a nested navigation stack.
@@ -16,14 +17,16 @@ import React from 'react';
1617
* - Sign out. Psuedo view to initiate signout flow. Navigates back to first layer.
1718
*/
1819
const HomeLayout = () => {
20+
const system = useSystem();
1921
return (
20-
<Stack screenOptions={{ headerTintColor: '#fff', headerStyle: { backgroundColor: '#2196f3' } }}>
21-
<Stack.Screen name="signin" options={{ title: 'Supabase Login' }} />
22-
<Stack.Screen name="register" options={{ title: 'Register' }} />
23-
24-
<Stack.Screen name="index" options={{ headerShown: false }} />
25-
<Stack.Screen name="views" options={{ headerShown: false }} />
26-
</Stack>
22+
<PowerSyncContext.Provider value={system.powersync}>
23+
<Stack screenOptions={{ headerTintColor: '#fff', headerStyle: { backgroundColor: '#2196f3' } }}>
24+
<Stack.Screen name="signin" options={{ title: 'Supabase Login' }} />
25+
<Stack.Screen name="register" options={{ title: 'Register' }} />
26+
<Stack.Screen name="index" options={{ headerShown: false }} />
27+
<Stack.Screen name="views" options={{ headerShown: false }} />
28+
</Stack>
29+
</PowerSyncContext.Provider>
2730
);
2831
};
2932

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import * as React from 'react';
22
import { ActivityIndicator, View } from 'react-native';
3-
import { observer } from 'mobx-react-lite';
4-
import { useSystem } from '../library/stores/system';
53
import { router } from 'expo-router';
64
import Logger from 'js-logger';
75
/**
@@ -10,34 +8,19 @@ import Logger from 'js-logger';
108
* - If one is present redirect to app views.
119
* - If not, reditect to login/register flow
1210
*/
13-
const App = observer(() => {
14-
const { djangoConnector } = useSystem();
15-
11+
const App = () => {
1612
React.useEffect(() => {
1713
Logger.useDefaults();
1814
Logger.setLevel(Logger.DEBUG);
1915

20-
const getSession = async () => {
21-
const response = await fetch('http://127.0.0.1:8000/api/get_session', {
22-
method: 'GET',
23-
headers: {
24-
'Content-Type': 'application/json'
25-
}
26-
});
27-
const data = await response.json();
28-
if (data) {
29-
router.replace('signin');
30-
}
31-
};
32-
33-
getSession();
16+
setImmediate(() => router.replace('signin'));
3417
}, []);
3518

3619
return (
3720
<View style={{ flex: 1, flexGrow: 1, alignContent: 'center', justifyContent: 'center' }}>
3821
<ActivityIndicator />
3922
</View>
4023
);
41-
});
24+
};
4225

4326
export default App;

demos/django-react-native-todolist/app/register.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useSystem } from '../library/stores/system';
66
import { TextInputWidget } from '../library/widgets/TextInputWidget';
77
import { SignInStyles } from './signin';
88
import { Icon } from 'react-native-elements';
9+
import { router } from 'expo-router';
910

1011
export default function Register() {
1112
const { djangoConnector } = useSystem();
@@ -24,6 +25,7 @@ export default function Register() {
2425

2526
<TextInputWidget
2627
placeholder="Username"
28+
autoCapitalize="none"
2729
onChangeText={(value) => setCredentials({ ...credentials, username: value })}
2830
/>
2931
<TextInputWidget
@@ -39,19 +41,8 @@ export default function Register() {
3941
setLoading(true);
4042
setError('');
4143
try {
42-
// const { data, error } = await djangoConnector.supabaseClient.auth.signUp({
43-
// email: credentials.username,
44-
// password: credentials.password
45-
// });
46-
// if (error) {
47-
// throw error;
48-
// }
49-
// if (data.session) {
50-
// // djangoConnector.supabaseClient.auth.setSession(data.session);
51-
// router.replace('views/todos/lists');
52-
// } else {
53-
// router.replace('signin');
54-
// }
44+
await djangoConnector.register(credentials.username, credentials.password);
45+
router.replace('signin');
5546
} catch (ex: any) {
5647
console.error(ex);
5748
setError(ex.message || 'Could not register');

demos/django-react-native-todolist/app/signin.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ export default function Signin() {
2424
<TextInputWidget
2525
style={SignInStyles.input}
2626
placeholder="Username"
27+
autoCapitalize='none'
2728
onChangeText={(value) => setCredentials({ ...credentials, username: value.toLowerCase().trim() })}
2829
/>
2930
<TextInputWidget
3031
style={SignInStyles.input}
3132
placeholder="Password"
3233
secureTextEntry={true}
34+
autoCapitalize='none'
3335
onChangeText={(value) => setCredentials({ ...credentials, password: value })}
3436
/>
3537
{error ? <Text style={{ color: 'red' }}>{error}</Text> : null}

demos/django-react-native-todolist/app/views/signout.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ import { router } from 'expo-router';
55
import { useSystem } from '../../library/stores/system';
66

77
export default function Signout() {
8-
const { powersync, supabaseConnector } = useSystem();
8+
const { powersync } = useSystem();
99

1010
React.useEffect(() => {
1111
(async () => {
1212
await powersync.disconnectAndClear();
13-
await supabaseConnector.supabaseClient.auth.signOut();
1413
router.replace('signin');
1514
})();
1615
}, []);
Lines changed: 15 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,29 @@
1-
import * as React from 'react';
2-
import { StatusBar } from 'expo-status-bar';
3-
import { ScrollView, View, Text } from 'react-native';
4-
import { FAB } from 'react-native-elements';
5-
import { observer } from 'mobx-react-lite';
6-
import { Stack, useLocalSearchParams } from 'expo-router';
7-
import prompt from 'react-native-prompt-android';
1+
import { View, Text, ActivityIndicator } from 'react-native';
2+
import { useLocalSearchParams } from 'expo-router';
3+
import { useQuery } from '@powersync/react';
4+
import { ListTodosWidget } from '../../../../library/widgets/ListTodosWidget';
5+
import { LIST_TABLE, ListRecord } from '../../../../library/powersync/AppSchema';
86

9-
import { useSystem } from '../../../../library/stores/system';
10-
import { TodoItemWidget } from '../../../../library/widgets/TodoItemWidget';
11-
12-
const TodoView = observer(() => {
13-
const { listStore, todoStore } = useSystem();
7+
const TodoView = () => {
148
const params = useLocalSearchParams<{ id: string }>();
159

1610
const id = params.id;
17-
const listModel = React.useMemo(() => {
18-
if (!id) {
19-
return null;
20-
}
21-
const listModel = listStore.getById(id);
22-
23-
return listModel;
24-
}, [id]);
11+
const { data: result, isLoading } = useQuery<ListRecord>(`SELECT * FROM ${LIST_TABLE} WHERE id = ?`, [id]);
12+
const listRecord = result[0];
2513

26-
if (!listModel) {
14+
if (!listRecord && !isLoading) {
2715
return (
2816
<View>
29-
<Text>No matching List found</Text>
17+
<Text>No matching list found</Text>
3018
</View>
3119
);
3220
}
3321

34-
return (
35-
<View style={{ flexGrow: 1 }}>
36-
<Stack.Screen
37-
options={{
38-
title: listModel.record.name
39-
}}
40-
/>
41-
<FAB
42-
style={{ zIndex: 99, bottom: 0 }}
43-
icon={{ name: 'add', color: 'white' }}
44-
size="small"
45-
placement="right"
46-
onPress={() => {
47-
prompt(
48-
'Add a new Todo',
49-
'',
50-
(text) => {
51-
if (!text) {
52-
return;
53-
}
54-
55-
todoStore.createModel({
56-
list_id: listModel.id,
57-
description: text,
58-
completed: false
59-
});
60-
},
61-
{ placeholder: 'Todo description', style: 'shimo' }
62-
);
63-
}}
64-
/>
65-
<ScrollView style={{ maxHeight: '90%' }}>
66-
{listModel.todos.map((t) => {
67-
return <TodoItemWidget key={t.id} model={t} />;
68-
})}
69-
</ScrollView>
22+
if (isLoading) {
23+
return <ActivityIndicator />;
24+
}
7025

71-
<StatusBar style={'light'} />
72-
</View>
73-
);
74-
});
26+
return <ListTodosWidget record={listRecord} />;
27+
};
7528

7629
export default TodoView;
Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
import * as React from 'react';
21
import { StatusBar } from 'expo-status-bar';
32
import { ScrollView, View } from 'react-native';
43
import { FAB } from 'react-native-elements';
5-
import { observer } from 'mobx-react-lite';
64
import prompt from 'react-native-prompt-android';
75

86
import { useSystem } from '../../../library/stores/system';
97
import { ListItemWidget } from '../../../library/widgets/ListItemWidget';
108
import { Stack } from 'expo-router';
9+
import { useQuery } from '@powersync/react';
10+
import { LIST_TABLE } from '../../../library/powersync/AppSchema';
11+
12+
const App = () => {
13+
const system = useSystem();
14+
const { data: lists } = useQuery(`SELECT * FROM ${LIST_TABLE}`);
1115

12-
const App = observer(() => {
13-
const { listStore } = useSystem();
1416
return (
1517
<View style={{ flex: 1, flexGrow: 1 }}>
1618
<Stack.Screen
@@ -27,28 +29,31 @@ const App = observer(() => {
2729
prompt(
2830
'Add a new list',
2931
'',
30-
(text) => {
32+
async (text) => {
3133
if (!text) {
3234
return;
3335
}
3436

35-
listStore.createModel({
36-
name: text
37-
});
37+
const { userID } = await system.djangoConnector.fetchCredentials();
38+
39+
await system.powersync.execute(
40+
`INSERT INTO ${LIST_TABLE} (id, created_at, name, owner_id) VALUES (uuid(), datetime(), ?, ?)`,
41+
[text, userID]
42+
);
3843
},
3944
{ placeholder: 'List name', style: 'shimo' }
4045
);
4146
}}
4247
/>
4348
<ScrollView style={{ maxHeight: '90%' }}>
44-
{listStore.collection.map((t) => (
45-
<ListItemWidget key={t.id} model={t} />
49+
{lists.map((t) => (
50+
<ListItemWidget key={t.id} record={t} />
4651
))}
4752
</ScrollView>
4853

4954
<StatusBar style={'light'} />
5055
</View>
5156
);
52-
});
57+
};
5358

5459
export default App;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
create publication powersync for table api_list, api_todo;
1+
create publication powersync for table lists, todos;

0 commit comments

Comments
 (0)