Skip to content

Commit

Permalink
Django RN demo app: fix some issues picked up during Django backend c…
Browse files Browse the repository at this point in the history
…leanup (#217)

Co-authored-by: Steven Ontong <[email protected]>
  • Loading branch information
kobiebotha and stevensJourney authored Jul 3, 2024
1 parent f58c287 commit 9842df6
Show file tree
Hide file tree
Showing 29 changed files with 393 additions and 623 deletions.
5 changes: 5 additions & 0 deletions .changeset/eighty-pots-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'django-react-native-todolist': minor
---

Use react hooks. Remove Mobx stores.
29 changes: 24 additions & 5 deletions demos/django-react-native-todolist/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,31 @@ Run on Android
pnpm android
```

## Set up Django Backend
## Service Configuration

This demo can be used with cloud or local services.

### Local Services

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.
See [instructions](https://github.com/powersync-ja/self-host-demo/blob/feature/django-backend/demos/django/README.md) for starting the backend locally.

#### Android

Note that Android requires port forwarding of local services. These can be configured with ADB as below:

```bash
adb reverse tcp:8080 tcp:8080 && adb reverse tcp:6061 tcp:6061
```

### Cloud Services

#### Set up Django Backend

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.
Follow the guide in the README of the PowerSync Django Backend to set it up.

## Set up PowerSync Instance
#### Set up PowerSync Instance

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

Expand All @@ -42,11 +61,11 @@ bucket_definitions:
# Separate bucket per todo list
parameters: select id as list_id from lists where owner_id = token_parameters.user_id
data:
- select * from api_list
- select * from api_todo
- select * from lists
- select * from todos
```
## Configure The App
#### Configure The App
Copy the `AppConfig.template.ts` to a usable file

Expand Down
19 changes: 11 additions & 8 deletions demos/django-react-native-todolist/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Stack } from 'expo-router';
import React from 'react';
import { PowerSyncContext } from '@powersync/react';
import { useSystem } from '../library/stores/system';

/**
* This App uses a nested navigation stack.
Expand All @@ -16,14 +17,16 @@ import React from 'react';
* - Sign out. Psuedo view to initiate signout flow. Navigates back to first layer.
*/
const HomeLayout = () => {
const system = useSystem();
return (
<Stack screenOptions={{ headerTintColor: '#fff', headerStyle: { backgroundColor: '#2196f3' } }}>
<Stack.Screen name="signin" options={{ title: 'Supabase Login' }} />
<Stack.Screen name="register" options={{ title: 'Register' }} />

<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="views" options={{ headerShown: false }} />
</Stack>
<PowerSyncContext.Provider value={system.powersync}>
<Stack screenOptions={{ headerTintColor: '#fff', headerStyle: { backgroundColor: '#2196f3' } }}>
<Stack.Screen name="signin" options={{ title: 'Supabase Login' }} />
<Stack.Screen name="register" options={{ title: 'Register' }} />
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="views" options={{ headerShown: false }} />
</Stack>
</PowerSyncContext.Provider>
);
};

Expand Down
23 changes: 3 additions & 20 deletions demos/django-react-native-todolist/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import * as React from 'react';
import { ActivityIndicator, View } from 'react-native';
import { observer } from 'mobx-react-lite';
import { useSystem } from '../library/stores/system';
import { router } from 'expo-router';
import Logger from 'js-logger';
/**
Expand All @@ -10,34 +8,19 @@ import Logger from 'js-logger';
* - If one is present redirect to app views.
* - If not, reditect to login/register flow
*/
const App = observer(() => {
const { djangoConnector } = useSystem();

const App = () => {
React.useEffect(() => {
Logger.useDefaults();
Logger.setLevel(Logger.DEBUG);

const getSession = async () => {
const response = await fetch('http://127.0.0.1:8000/api/get_session', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data) {
router.replace('signin');
}
};

getSession();
setImmediate(() => router.replace('signin'));
}, []);

return (
<View style={{ flex: 1, flexGrow: 1, alignContent: 'center', justifyContent: 'center' }}>
<ActivityIndicator />
</View>
);
});
};

export default App;
17 changes: 4 additions & 13 deletions demos/django-react-native-todolist/app/register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useSystem } from '../library/stores/system';
import { TextInputWidget } from '../library/widgets/TextInputWidget';
import { SignInStyles } from './signin';
import { Icon } from 'react-native-elements';
import { router } from 'expo-router';

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

<TextInputWidget
placeholder="Username"
autoCapitalize="none"
onChangeText={(value) => setCredentials({ ...credentials, username: value })}
/>
<TextInputWidget
Expand All @@ -39,19 +41,8 @@ export default function Register() {
setLoading(true);
setError('');
try {
// const { data, error } = await djangoConnector.supabaseClient.auth.signUp({
// email: credentials.username,
// password: credentials.password
// });
// if (error) {
// throw error;
// }
// if (data.session) {
// // djangoConnector.supabaseClient.auth.setSession(data.session);
// router.replace('views/todos/lists');
// } else {
// router.replace('signin');
// }
await djangoConnector.register(credentials.username, credentials.password);
router.replace('signin');
} catch (ex: any) {
console.error(ex);
setError(ex.message || 'Could not register');
Expand Down
2 changes: 2 additions & 0 deletions demos/django-react-native-todolist/app/signin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ export default function Signin() {
<TextInputWidget
style={SignInStyles.input}
placeholder="Username"
autoCapitalize='none'
onChangeText={(value) => setCredentials({ ...credentials, username: value.toLowerCase().trim() })}
/>
<TextInputWidget
style={SignInStyles.input}
placeholder="Password"
secureTextEntry={true}
autoCapitalize='none'
onChangeText={(value) => setCredentials({ ...credentials, password: value })}
/>
{error ? <Text style={{ color: 'red' }}>{error}</Text> : null}
Expand Down
3 changes: 1 addition & 2 deletions demos/django-react-native-todolist/app/views/signout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import { router } from 'expo-router';
import { useSystem } from '../../library/stores/system';

export default function Signout() {
const { powersync, supabaseConnector } = useSystem();
const { powersync } = useSystem();

React.useEffect(() => {
(async () => {
await powersync.disconnectAndClear();
await supabaseConnector.supabaseClient.auth.signOut();
router.replace('signin');
})();
}, []);
Expand Down
77 changes: 15 additions & 62 deletions demos/django-react-native-todolist/app/views/todos/edit/[id].tsx
Original file line number Diff line number Diff line change
@@ -1,76 +1,29 @@
import * as React from 'react';
import { StatusBar } from 'expo-status-bar';
import { ScrollView, View, Text } from 'react-native';
import { FAB } from 'react-native-elements';
import { observer } from 'mobx-react-lite';
import { Stack, useLocalSearchParams } from 'expo-router';
import prompt from 'react-native-prompt-android';
import { View, Text, ActivityIndicator } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
import { useQuery } from '@powersync/react';
import { ListTodosWidget } from '../../../../library/widgets/ListTodosWidget';
import { LIST_TABLE, ListRecord } from '../../../../library/powersync/AppSchema';

import { useSystem } from '../../../../library/stores/system';
import { TodoItemWidget } from '../../../../library/widgets/TodoItemWidget';

const TodoView = observer(() => {
const { listStore, todoStore } = useSystem();
const TodoView = () => {
const params = useLocalSearchParams<{ id: string }>();

const id = params.id;
const listModel = React.useMemo(() => {
if (!id) {
return null;
}
const listModel = listStore.getById(id);

return listModel;
}, [id]);
const { data: result, isLoading } = useQuery<ListRecord>(`SELECT * FROM ${LIST_TABLE} WHERE id = ?`, [id]);
const listRecord = result[0];

if (!listModel) {
if (!listRecord && !isLoading) {
return (
<View>
<Text>No matching List found</Text>
<Text>No matching list found</Text>
</View>
);
}

return (
<View style={{ flexGrow: 1 }}>
<Stack.Screen
options={{
title: listModel.record.name
}}
/>
<FAB
style={{ zIndex: 99, bottom: 0 }}
icon={{ name: 'add', color: 'white' }}
size="small"
placement="right"
onPress={() => {
prompt(
'Add a new Todo',
'',
(text) => {
if (!text) {
return;
}

todoStore.createModel({
list_id: listModel.id,
description: text,
completed: false
});
},
{ placeholder: 'Todo description', style: 'shimo' }
);
}}
/>
<ScrollView style={{ maxHeight: '90%' }}>
{listModel.todos.map((t) => {
return <TodoItemWidget key={t.id} model={t} />;
})}
</ScrollView>
if (isLoading) {
return <ActivityIndicator />;
}

<StatusBar style={'light'} />
</View>
);
});
return <ListTodosWidget record={listRecord} />;
};

export default TodoView;
27 changes: 16 additions & 11 deletions demos/django-react-native-todolist/app/views/todos/lists.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import * as React from 'react';
import { StatusBar } from 'expo-status-bar';
import { ScrollView, View } from 'react-native';
import { FAB } from 'react-native-elements';
import { observer } from 'mobx-react-lite';
import prompt from 'react-native-prompt-android';

import { useSystem } from '../../../library/stores/system';
import { ListItemWidget } from '../../../library/widgets/ListItemWidget';
import { Stack } from 'expo-router';
import { useQuery } from '@powersync/react';
import { LIST_TABLE } from '../../../library/powersync/AppSchema';

const App = () => {
const system = useSystem();
const { data: lists } = useQuery(`SELECT * FROM ${LIST_TABLE}`);

const App = observer(() => {
const { listStore } = useSystem();
return (
<View style={{ flex: 1, flexGrow: 1 }}>
<Stack.Screen
Expand All @@ -27,28 +29,31 @@ const App = observer(() => {
prompt(
'Add a new list',
'',
(text) => {
async (text) => {
if (!text) {
return;
}

listStore.createModel({
name: text
});
const { userID } = await system.djangoConnector.fetchCredentials();

await system.powersync.execute(
`INSERT INTO ${LIST_TABLE} (id, created_at, name, owner_id) VALUES (uuid(), datetime(), ?, ?)`,
[text, userID]
);
},
{ placeholder: 'List name', style: 'shimo' }
);
}}
/>
<ScrollView style={{ maxHeight: '90%' }}>
{listStore.collection.map((t) => (
<ListItemWidget key={t.id} model={t} />
{lists.map((t) => (
<ListItemWidget key={t.id} record={t} />
))}
</ScrollView>

<StatusBar style={'light'} />
</View>
);
});
};

export default App;
2 changes: 1 addition & 1 deletion demos/django-react-native-todolist/database.sql
Original file line number Diff line number Diff line change
@@ -1 +1 @@
create publication powersync for table api_list, api_todo;
create publication powersync for table lists, todos;
Loading

0 comments on commit 9842df6

Please sign in to comment.