Simulator.Screen.Recording.-.iPhone.8.-.2021-08-05.at.21.00.58_.mp4
- Start med at oprette et nyt projekt som vi plejer.
npx create-expo-app@latest --template blank
- Installér følgende dependencies med
npx expo install eller npm install
;npx expo install @react-navigation/native @react-navigation/bottom-tabs @react-navigation/stack react-native-vector-icons firebase
- Opret nu en authentication-database i Firebase;
-
Følg dette link: https://firebase.google.com/
-
Tryk på "Get Started"
-
Tryk på "Create a Project"
-
Giv projektet et vilkårligt navn og tryk "continue"
-
Fjern Analytics og tryk "create project"
-
Registrer nu en web Aapplikation. Tryk på ikonet </> - Se billeder herunder (billederne er ikke helt opdateret men processen er ens);
-
Giv applikationen et vilkårligt navn og tryk "Register app".
-
Gå ind under real time database, og tryk create database.
-
Tryk nu videre og vælg testmode.
-
Gå nu til settings og gå ned i bunden og kopier firebaseConfig-kodestykket nedenfor
- Indsæt dette i app.js, efter dine imports.
- Opret nu en
components
mappe og opret følgende 3 komponenter i den mappe. Brug skabelon 1 i alle tre- Add_edit_Car.js
- CarDetails.js
- CarList.js
- Husk at importere de nødvendige komponenter fra node modules, som
Text
fra react native, præcis som i plejer i alle filer - Husk også at komponentnavnet skal være ens med filnavnet
- Importer de nødvendige komponenter som vi plejer
2. Hint:
import { getApps, initializeApp } from "firebase/app"; import {createStackNavigator} from "@react-navigation/stack"; import {createBottomTabNavigator} from "@react-navigation/bottom-tabs";
- Efter din const af firebase Configarationen (som du kopieret fra Firebase før), skal Firebase initialiseres:
// Vi kontrollerer at der ikke allerede er en initialiseret instans af firebase // Så undgår vi fejlen Firebase App named '[DEFAULT]' already exists (app/duplicate-app). if (getApps().length < 1) { initializeApp(firebaseConfig); console.log("Firebase On!"); // Initialize other firebase products here }
- Efter overstående kode på App.js, opret en Stack navigatoren med
const Stack = createStackNavigator();
- Lav derefter en funktion kaldet StackNavigation, som skal returnere 3 Screens med "name" samt komponentnavnene CarList, CarDetails og Add_edit_car
- Det er vigtigt at CarList placeres øverst ;) (Dette er den første skærm brugeren bliver præsenteret med på denne screen)
- Se evt. https://reactnavigation.org/docs/stack-navigator/#api-definition
- Du kan også se eksempler i tidligere øvelser
- Oppe ved
const Stack = createStackNavigator()
, Initialiser deraf en Bottom navigatorer medconst Tab = createBottomTabNavigator();
- Gå nu til return opret en
<NavigationContainer></NavigationContainer>
, som skal wrappe din Tab.navigator --> Se https://reactnavigation.org/docs/bottom-tab-navigator/ & https://reactnavigation.org/docs/navigation-container - I Tab.Navigator wrapper oprettes to Tab.Screen med name og component, som henholdvis skal være din StackNavigation og Add_edit_Car
- Tilføj et ikon til Home Tab.Screen | Eksempel ( husk at Ionicons skal importeres)
<Tab.Screen name={'Home'} component={StackNavigation} options={{tabBarIcon: () => ( <Ionicons name="home" size={20} />),headerShown:null}}/>
- Tilføj nu også et ikon til "Add" Screenen, hvor ikon navnet er
add
istedet for home - Tilføj nu din
BottomNavigation
i en endeligreturn
function. - Nu burde du kunne trykke imellem add og stacknavigatoren "Carlist"
Hint:
export default function App() {
const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();
const StackNavigation = () => {
return(
<Stack.Navigator>
<Stack.Screen "din kode her" />
<Stack.Screen "din kode her" />
<Stack.Screen "din kode her" />
</Stack.Navigator>
)
}
const BottomNavigation = () => {
return(
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen "din kode her" />
<Tab.Screen "din kode her" />
</Tab.Navigator>
</NavigationContainer>
)
}
return (
<BottomNavigation/>
);
}
}
-
Import de nødvendige komponenter som vi plejer 2. Hint:
import {useEffect, useState} from "react"; import { getDatabase, ref, child, push, update } from "firebase/database";
-
I parameter parantesen indsæt nu
{navigation,route}
i stedet forprops
, så vi henter fra react navigations parametre som skal sendes videre fra forskellige Screens- Hint:
const Add_edit_Car = ({navigation,route}) => {}
- Hint:
-
Øverst i din function, lav en db constant med
const db = getDatabase();
-
Lav derefter en initialState object med tilhørende string attributter: brand,model,year og licensplate
const initialState = { brand: '', model: '', year: '', licensePlate: '' }
-
States |Lav nedenunder en ny State kaldet
[newCar,setNewCar]
, hvor du i useState brugerinitialState
const [newCar,setNewCar] = useState(initialState);
-
Lav derunder en const funktion kaldet isEditCar:
const isEditCar = route.name === "Edit car"
eller hvad du nu har kaldt dette screen name i stacknavigatoren i app.js- ( Skal bruges til senere )
-
Opret nu en changeTextInput funktion
const changeTextInput = (name,event) => { setNewCar({...newCar, [name]: event}); }
-
Content | Gå til return og lav et SafeAreaView som parent, og deri et ScrollView
return ( <SafeAreaView style={styles.container}> <ScrollView> </ScrollView> </SafeAreaView> );
Nu laver vi et über powermove med JS, hvor vi i vores return funktion skal returnere en række felter som vi skal kunne indtaste og ændre værdier i
- Du har et objekt, som indeholder nogle nøgler (f.eks.
initialState
). - Din opgave er at bruge
Object.keys()
for at få en liste over nøglerne fra objektet. - Tip:
Object.keys()
returnerer et array med alle nøglerne fra objektet, som du kan iterere over.
- Brug
.map()
for at iterere gennem nøglerne fraObject.keys()
og generere et inputfelt for hver nøgle. - Hvert inputfelt skal have en tilhørende
Text
-komponent, der viser nøglens navn. - Husk at give hvert felt en unik nøgle baseret på nøglen eller indekset i arrayet.
- For hvert felt skal du sørge for, at værdien i
TextInput
svarer til det pågældende element i objektet (f.eks.newCar
). - Brug
onChangeText
-funktionen til at opdatere objektet, når brugeren ændrer indholdet i feltet. - Tip: Du skal bruge både nøglen og den indtastede værdi (event) for at opdatere det korrekte felt i objektet.
- Hvert felt skal placeres i en
View
-container med enText
-komponent til at vise nøglen og etTextInput
-felt, hvor brugeren kan indtaste en værdi. - Tænk over: Hvordan vil du sikre, at både nøgle og værdi er korrekt bundet til inputfeltet? Du skal opdatere en specifik nøgle i objektet, når brugeren skriver i feltet.
- Du har et objekt, som indeholder nogle nøgler (f.eks.
- Hver nøgle skal vises i et tekstfelt, og det tilhørende inputfelt skal være bundet til objektets værdi.
- Tænk over, hvordan du kan bruge
newCar[key]
til at binde værdien tilTextInput
og hvordan du vil opdatere værdien medonChangeText
. - Se eventuelt nede i hints
powermove JS funktion 2
eller check i nedestående hint i HandleSave
- Lav nu en button som har en save changes funktion kaldet
handlesave()
, og giv en titleAdd car
- Hint til knappen:
return (
<SafeAreaView style={styles.container}>
<ScrollView>
{
Object.keys(initialState).map((key,index) =>{
return(
<View style={styles.row} key={index}>
<Text style={styles.label}>{key}</Text>
<TextInput
value={????[key]}
onChangeText={(event) => changeTextInput(key,event)}
style={styles.input}
/>
</View>
)
})
}
{/*Hvis vi er inde på edit car, vis save changes i stedet for add car*/}
<Button title={ isEditCar ? "Save changes" : "Add car"} onPress={() => handleSave()} />
</ScrollView>
</SafeAreaView>
);
- Kopierer følgende handleSave funktion og indsæt det før din
return
:
const handleSave = async () => {
const { brand, model, year, licensePlate } = newCar;
if(brand.length === 0 || model.length === 0 || year.length === 0 || licensePlate.length === 0 ){
return Alert.alert('Et af felterne er tomme!');
}
if(isEditCar){
const id = route.params.car[0];
// Define the path to the specific car node you want to update
const carRef = ref(db, `Cars/${id}`);
// Define the fields you want to update
const updatedFields = {
brand,
model,
year,
licensePlate,
};
// Use the 'update' function to update the specified fields
await update(carRef, updatedFields)
.then(() => {
Alert.alert("Din info er nu opdateret");
const car = newCar
navigation.navigate("Car Details", { car });
})
.catch((error) => {
console.error(`Error: ${error.message}`);
});
}else{
// Define the path to the "Cars" node where you want to push the new data
const carsRef = ref(db, "/Cars/");
// Data to push
const newCarData = {
brand,
model,
year,
licensePlate,
};
// Push the new data to the "Cars" node
await push(carsRef, newCarData)
.then(() => {
Alert.alert("Saved");
setNewCar(initialState);
})
.catch((error) => {
console.error(`Error: ${error.message}`);
});
}
};
-
Brug lidt tid til at forstå hvad koden gør. Evt. få en LLM til at forklare dig det i bider.
-
Nå du trykker på button i din app burde du nu få en alert med, at du har saved. Gå nu ind i firebase (console.firebase.com) for at se, om din bil er blevet oprettet
-
Importer de nødvendige komponenter som vi plejer
-
Indsæt
{navigation}
i parameter parentesen i stedet for props, så vi henter fra react navigations parametre som skal sendes videre fra forskellige Screens 3. Hint:function CarList = ({navigation}) => {}
-
Opret en state for
[cars, setCars]
meduseState()
-
Lav nu en
useEffect
funktion, hvori der laves en if(!cars) og derefter indsætter denne kode:useEffect(() => { const db = getDatabase(); const carsRef = ref(db, "Cars"); // Use the 'onValue' function to listen for changes in the 'Cars' node onValue(carsRef, (snapshot) => { const data = snapshot.val(); if (data) { // If data exists, set it in the 'cars' state setCars(data); } }); // Clean up the listener when the component unmounts return () => { // Unsubscribe the listener off(carsRef); }; }, []); // The empty dependency array means this effect runs only once // Vi viser ingenting hvis der ikke er data if (!cars) { return <Text>Loading...</Text>; }
- Husk at useEffect kun skal køres en gang med et tomt array til sidst
[]);
- Husk at useEffect kun skal køres en gang med et tomt array til sidst
-
Efter useEffect laves et if statement som kontrollerer, om der ikke er nogle biler og returner
// Vi viser ingenting hvis der ikke er data if (!cars) { return <Text>Loading...</Text>; }
-
Lav nu en funktion kaldet
const handleSelectCar = id => {}
, som søger direkte i vores array af biler og finder bilobjektet, som matcher idet vi har tilsendt og ligger det ned i vores parametre i navigationconst handleSelectCar = id => { /*Her søger vi direkte i vores array af biler og finder bil objektet som matcher idet vi har tilsendt*/ const car = Object.entries(cars).find( car => car[0] === id /*id*/) navigation.navigate('Car Details', { car }); };
-
Over return funktionen her skal vi have to const variabler
carArray
med værdierObject.values()
, ogcarKeys
medObejct.keys()
// Flatlist forventer et array. Derfor tager vi alle values fra vores cars objekt, og bruger som array til listen const carArray = Object.values(cars); const carKeys = Object.keys(cars);
-
I Return funktionen vil vi lave en
<FlatList/>
, hvori der skal laves følgende tre attributterdata={carArray}
keyExtractor
med en funktion med parametrene item, index som sættes medcarKeys[index]
keyExtractor={(item, index) => carKeys[index]}
)
renderItem
med følgende return funktion:({item,index}) => { return() }
.- I return funktionen skal vi have et parent
TouchableOpacity
med en onpress funktion:handleSelectCar(carKeys[index])
- lav et Text element med
{item.brand}
- Hint
<TouchableOpacity style={styles.container} onPress={() => handleSelectCar(carKeys[index])}>
- lav et Text element med
- Hint:
return ( <FlatList data={carArray} // Vi bruger carKeys til at finde ID på den aktuelle bil og returnerer dette som key, og giver det med som ID til CarListItem keyExtractor={(item, index) => carKeys[index]} renderItem={({ item, index }) => { return( <TouchableOpacity style={styles.container} onPress={() => handleSelectCar(carKeys[index])}> <Text> {item.brand} {item.model} </Text> </TouchableOpacity> ) }} /> );
-
Nu skulle du gerne kunne trykke på bil-modellen, du har oprettet i tidligere step og gå til car details
-
Impoter de nødvendige komponenter som vi plejer
- Hint:
import {useEffect, useState} from "react"; import { getDatabase, ref, remove } from "firebase/database";
- Hint:
-
Start med at indsætte
{navigation,route}
i stedet for props ved CarDetails præcis som i de andre komponenter -
Opret en tom state for cars med
useState
-
Lav nu en
useEffect
, og hent car values og sæt dem medsetCar(route.params.car[1])
. Når vi forlader screenen, skal objektet tømmes:return () => { setCar({}) }
- Hint:
useEffect(() => { setCar(route.params.car[1]) return () => { setCar({}) }; }, []);
- Hint:
-
Opret nu en funktion kaldet
handleEdit
, som henter car objektet fraroute.params.car
, og sæt det i navigation, så vi navigerer til 'Edit Car' - hint: kig tilbage i navigationsøvelsen- hint:
const handleEdit = () => { // Vi navigerer videre til EditCar skærmen og sender bilen videre med const car = route.params.car navigation.navigate('Edit Car', { car }); };
-
Lav nu en
confirmDelete
funktion hvor du laver enAlert.alert
. Vi fremviser vores native alert afhængig af hvilke system brugeren er på:// Vi spørger brugeren om han er sikker const confirmDelete = () => { /*Er det mobile?*/ if(Platform.OS ==='ios' || Platform.OS ==='android'){ Alert.alert('Are you sure?', 'Do you want to delete the car?', [ { text: 'Cancel', style: 'cancel' }, // Vi bruger this.handleDelete som eventHandler til onPress { text: 'Delete', style: 'destructive', onPress: () => handleDelete() }, ]); } };
-
Lav nu en handleDelete funktion, som først henter id fra route med
route.params.car[0]
og lav en try catch, hvor i try'en kaldes næsten den samme firebase funktion som du har hentet "cars" med tidligere, men til sidst tilføj.remove()
og i ref'en ændres/Cars/${id}
. I catchen, skal parametren tageerror
som argument og inde i catchen laves en Alert med error.message- hint:
const handleDelete = async () => { const id = route.params.car[0]; const db = getDatabase(); // Define the path to the specific car node you want to remove const carRef = ref(db, `Cars/${id}`); // Use the 'remove' function to delete the car node await remove(carRef) .then(() => { navigation.goBack(); }) .catch((error) => { Alert.alert(error.message); }); };
-
Nedenfor laves nu et
if
statemtent: hvis der ikke er en car, skal vi returnere et text element med vilkårlig tekstif (!car) { return <Text>No data</Text>; }
-
I return funktionen laves to knapper, som kalder på hhv
handleEdit()
, ogconfirmDelete()
-
Prøv at forstå følgende, og indsæt derefter knapper
Object.entries(car).map((item,index)=>{ return( <View style={styles.row} key={index}> {/*Vores car keys navn*/} <Text style={styles.label}>{item[0]} </Text> {/*Vores car values navne */} <Text style={styles.value}>{item[1]}</Text> </View> ) })
-
Nu burde du se alle bilens informationer og kunne slette din bil. Og du burde kunne gå til edit/add screen
-
Nu vil vi gerne have, at når vi kommer fra CarDetails, så tager den vores bil med og indsætter værdierne i felterne
-
Opret nu en
useEffect
med en funktion- Her i denne funktion skal der først oprettes et if statement, med
isEditCar
- I
if
funktionen, så skal du hente din bils objekt fra paramentreneroute.params.car[1]
ogsetNewCar(car)
- Dernæst lav en return med en funktion, som siger
setNewCar(initalState)
for at fjerne data, når vi går væk fra screenen - Husk et tomt array til sidst i
useEffecten
- Her i denne funktion skal der først oprettes et if statement, med
-
Dernæst i
handleSave
lav etif else
statement- i if'en brug
isEditCar
, og deri lav en try catch som tager udgangspunkt i følgende https://firebase.google.com/docs/database/web/read-and-write - dernæst i else'en lig add funktionen
- i if'en brug
-
Gå nu ned i titlen på button nederst og sæt et isEditCar ? "Save changes" : "Add car" i titlen
-
done Hint:
const [newCar,setNewCar] = useState(initialState);
/*Returnere true, hvis vi er på edit car*/
const isEditCar = route.name === "Edit Car";
useEffect(() => {
if(isEditCar){
const car = route.params.car[1];
setNewCar(car)
}
/*Fjern data, når vi går væk fra screenen*/
return () => {
setNewCar(initialState)
};
}, []);
const changeTextInput = (name,event) => {
setNewCar({...newCar, [name]: event});
}
const handleSave = async () => {
const { brand, model, year, licensePlate } = newCar;
if(brand.length === 0 || model.length === 0 || year.length === 0 || licensePlate.length === 0 ){
return Alert.alert('Et af felterne er tomme!');
}
if(isEditCar){
const id = route.params.car[0];
// Define the path to the specific car node you want to update
const carRef = ref(db, `Cars/${id}`);
// Define the fields you want to update
const updatedFields = {
brand,
model,
year,
licensePlate,
};
// Use the 'update' function to update the specified fields
await update(carRef, updatedFields)
.then(() => {
Alert.alert("Din info er nu opdateret");
const car = newCar
navigation.navigate("Car Details", { car });
})
.catch((error) => {
console.error(`Error: ${error.message}`);
});
}else{
// Define the path to the "Cars" node where you want to push the new data
const carsRef = ref(db, "/Cars/");
// Data to push
const newCarData = {
brand,
model,
year,
licensePlate,
};
// Push the new data to the "Cars" node
await push(carsRef, newCarData)
.then(() => {
Alert.alert("Saved");
setNewCar(initialState);
})
.catch((error) => {
console.error(`Error: ${error.message}`);
});
}
const KomponentNavn = (props) => {
return (
<Text>Det er mit KomponentNavn</Text>
)
}
export default KomponentNavn;
Object.keys(initialState).map((key,index) =>{
return(
<View style={styles.row} key={index}>
<Text style={styles.label}>{key}</Text>
<TextInput
value={newCar[key]}
onChangeText={(event) => changeTextInput(key,event)}
style={styles.input}
/>
</View>
)
})
https://reactnavigation.org/docs/stack-navigator/ https://reactnavigation.org/docs/bottom-tab-navigator/ UseEffect:https://reactjs.org/docs/hooks-effect.html