TodosApp: Sample CRUD React-Native Expo and Firebase
Di kantor saya kedatangan mahasiswa magang, dan saya ditugaskan untuk mengajar mahasiswa tersebut tentang aplikasi mobile menggunakan React. Dalam training karena kurang persiapan, saya harus mencari bahan sample code menggunakan firebase, berbagai kode yang ditemuai terdapat berbagai kekurangan untuk mendemonstrasikan CRUD menggunakan firebase.
Saya temukan aplikasi sederhana TodosApp menggunakan framework Expo dan Firebase sebagai penyimpanan data. Tapi dimodifikasi sedikit agar dapat langsung digunakan melalui Snack di browser.
Ayo kita langsung saja bahas source code aplikasinya:
Config.js
Copy konfigurasi firebase ke file config.js dan buat collection di firestore todos, maka Anda sudah bisa menjalankan aplikasi TodosApp ini di Snack Expo
import firebase from 'firebase';
const firebaseConfig = {
apiKey: "################",
authDomain: "###################",
projectId: "###############",
storageBucket: "#####################",
messagingSenderId: "################",
appId: "######################",
measurementId: "##############"
};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
}
export { firebase };
App.js
App.js file utama tempat pengaturan navigasi aplikasi yang menggunakan tipe navigasi Stack
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import Home from './Screens/Home';
import Detail from './Screens/Detail';
const Stack = createStackNavigator()
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen
name='Home'
component={Home}
/>
<Stack.Screen
name='Detail'
component={Detail}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
Home.js
Pada screen home, terdapat query mengambil data pada saat loading screen yaitu di fungsi useEffect, kemudian ada juga fungsi tambah dan hapus data, khusus untuk edit data dilanjutkan di screen Detail
import React, { useState, useEffect } from 'react';
import {
View,
Text,
FlatList,
StyleSheet,
TextInput,
TouchableOpacity,
Keyboard,
} from 'react-native';
import { firebase } from '../config';
import { FontAwesome } from '@expo/vector-icons';
import { useNavigation } from '@react-navigation/native';
import { SafeAreaView } from 'react-native-safe-area-context';
const Home = () => {
const [todos, setTodos] = useState([]);
const todoRef = firebase.firestore().collection('todos');
const [addData, setAddData] = useState('');
const navigation = useNavigation();
// fetch or read the data from firestore
useEffect(() => {
todoRef.orderBy('createdAt', 'desc').onSnapshot((querySnapshot) => {
const todos = [];
querySnapshot.forEach((doc) => {
const { heading } = doc.data();
todos.push({
id: doc.id,
heading,
});
});
setTodos(todos);
//console.log(users)
});
}, []);
// delete a todo from firestore db
const deleteTodo = (todos) => {
todoRef
.doc(todos.id)
.delete()
.then(() => {
// show a successful alert
alert('Deleted successfully');
})
.catch((error) => {
// show an error alert
alert(error);
});
};
// add a todo
const addTodo = () => {
// check if we have a todo.
if (addData && addData.length > 0) {
// get the timestamp
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
const data = {
heading: addData,
createdAt: timestamp,
};
todoRef
.add(data)
.then(() => {
// release todo state
setAddData('');
// release keyboard
Keyboard.dismiss();
})
.catch((error) => {
// show an alert in case of error
alert(error);
});
}
};
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={styles.textHeadingContainer}>
<Text style={styles.textHeading}>Todos App</Text>
</View>
<View style={styles.formContainer}>
<TextInput
style={styles.input}
placeholder="Add new todo"
placeholderTextColor="#aaaaaa"
onChangeText={(heading) => setAddData(heading)}
value={addData}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<TouchableOpacity style={styles.button} onPress={addTodo}>
<Text style={styles.buttonText}>Add</Text>
</TouchableOpacity>
</View>
<FlatList
style={{}}
data={todos}
numColumns={1}
renderItem={({ item }) => (
<View>
<TouchableOpacity
style={styles.container}
onPress={() => navigation.navigate('Detail', { item })}>
<FontAwesome
name="trash-o"
color="red"
onPress={() => deleteTodo(item)}
style={styles.todoIcon}
/>
<View style={styles.innerContainer}>
<Text style={styles.itemHeading}>
{item.heading}
</Text>
</View>
</TouchableOpacity>
</View>
)}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: '#e5e5e5',
padding: 15,
borderRadius: 15,
margin: 5,
marginHorizontal: 10,
flexDirection: 'row',
alignItems: 'center',
},
textHeadingContainer: {
paddingVertical: 20,
alignContent: 'center',
alignItems: 'center',
},
textHeading: {
fontWeight: 'bold',
fontSize: 24,
},
innerContainer: {
alignItems: 'center',
flexDirection: 'column',
marginLeft: 45,
},
itemHeading: {
fontWeight: 'bold',
fontSize: 18,
marginRight: 22,
},
formContainer: {
flexDirection: 'row',
height: 80,
marginLeft: 10,
marginRight: 10,
marginTop: 40,
},
input: {
height: 48,
borderRadius: 5,
overflow: 'hidden',
backgroundColor: 'white',
paddingLeft: 16,
flex: 1,
marginRight: 5,
},
button: {
height: 47,
borderRadius: 5,
backgroundColor: '#788eec',
width: 80,
alignItems: 'center',
justifyContent: 'center',
},
buttonText: {
color: 'white',
fontSize: 20,
},
todoIcon: {
marginTop: 5,
fontSize: 20,
marginLeft: 14,
},
});
export default Home;
Detail.js
Screen Detail mengambil data dari kiriman parameter Home (route.params.item), yang kemudian ditampilkan di field input, terakhir user dapat mengedit dan menyimpan setelah terjadi perubahan.
import React, { useState } from 'react';
import {
View,
TextInput,
StyleSheet,
Text,
TouchableOpacity,
} from 'react-native';
import { firebase } from '../config';
import { useNavigation } from '@react-navigation/native';
const Detail = ({ route }) => {
const todoRef = firebase.firestore().collection('todos');
const [textHeading, onChangeHeadingText] = useState(
route.params.item.heading
);
const navigation = useNavigation();
const updateTodo = () => {
if (textHeading && textHeading.length > 0) {
todoRef
.doc(route.params.item.id)
.update({
heading: textHeading,
})
.then(() => {
navigation.navigate('Home');
})
.catch((error) => {
alert(error.message);
});
}
};
return (
<View style={styles.container}>
<TextInput
style={styles.textfield}
onChangeText={onChangeHeadingText}
value={textHeading}
placeholder="Update Todo"
/>
<TouchableOpacity
style={styles.buttonUpdate}
onPress={() => {
updateTodo();
}}>
<Text>UPDATE</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
marginTop: 80,
marginLeft: 15,
marginRight: 15,
},
textfield: {
marginBottom: 10,
padding: 10,
fontSize: 15,
color: '#000000',
backgroundColor: '#e0e0e0',
borderRadius: 5,
},
buttonUpdate: {
marginTop: 25,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 12,
paddingHorizontal: 32,
borderRadius: 4,
elevation: 10,
backgroundColor: '#0de065',
},
});
export default Detail;
Github:
https://github.com/rudiahmad/crud-react-native-expo-and-firebase-todosapp
Perubahan dari sumber code:
- Change database version from 9 to 8
- Mengubah coding supaya bisa jalan langsung dari Expo Snack
Expo Snack
https://snack.expo.dev/@rudiahmad/crud-react-native-expo-and-firebase-todosapp
Sumber code :
https://github.com/imrohit007/CRUD-React-Native-Expo-And-Firebase