Choose a photo
Let's see how we could store a plant's photo along with it's details.
Pick the image
For this we'll use image picker that lets you choose images both from the image Gallery as well a take a picture.
Install the image picker library
npx expo install expo-image-picker
Launch image picker
Update: app/new.tsx
@@ -1,4 +1,10 @@
-import { Text, StyleSheet, TextInput, Alert, View } from "react-native";
+import {
+ Text,
+ StyleSheet,
+ TextInput,
+ Alert,
+ TouchableOpacity,
+ Platform
+} from "react-native";
import { theme } from "@/theme";
import { PlantlyButton } from "@/components/PlantlyButton";
import { useState } from "react";
@@ -6,12 +12,14 @@ import { PlantlyImage } from "@/components/PlantlyImage";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import { usePlantStore } from "@/store/plantsStore";
import { useRouter } from "expo-router";
+import * as ImagePicker from "expo-image-picker";
export default function NewScreen() {
const router = useRouter();
const addPlant = usePlantStore((state) => state.addPlant);
const [name, setName] = useState<string>();
const [days, setDays] = useState<string>();
+ const [imageUri, setImageUri] = useState<string>();
const handleSubmit = () => {
if (!name) {
@@ -36,15 +44,32 @@ export default function NewScreen() {
router.navigate("/");
};
+ const handleChooseImage = async () => {
+ if (Platform.OS === "web") {
+ return;
+ }
+
+ const result = await ImagePicker.launchImageLibraryAsync({
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
+ allowsEditing: true,
+ aspect: [1, 1],
+ quality: 1,
+ });
+
+ if (!result.canceled) {
+ setImageUri(result.assets[0].uri);
+ }
+ };
+
return (
<KeyboardAwareScrollView
style={styles.container}
contentContainerStyle={styles.contentContainer}
keyboardShouldPersistTaps="handled"
>
- <View style={styles.centered}>
- <PlantlyImage />
- </View>
+ <TouchableOpacity
+ style={styles.centered}
+ onPress={handleChooseImage}
+ activeOpacity={0.8}
+ >
+ <PlantlyImage imageUri={imageUri} />
+ </TouchableOpacity>
<Text style={styles.label}>Name</Text>
<TextInput
value={name}
@@ -90,5 +115,6 @@ const styles = StyleSheet.create({
},
centered: {
alignItems: "center",
+ marginBottom: 24,
},
});
Show custom image in PlantlyImage
Update: components/PlantlyImage.tsx
@@ -1,14 +1,23 @@
import { Image, useWindowDimensions } from "react-native";
-export function PlantlyImage({ size }: { size?: number }) {
+type Props = {
+ size?: number;
+ imageUri?: string;
+};
+
+export function PlantlyImage({ size, imageUri }: Props) {
const { width } = useWindowDimensions();
const imageSize = size || Math.min(width / 1.5, 400);
return (
<Image
- source={require("@/assets/plantly.png")}
- style={{ width: imageSize, height: imageSize }}
+ source={imageUri ? { uri: imageUri } : require("@/assets/plantly.png")}
+ style={{
+ width: imageSize,
+ height: imageSize,
+ borderRadius: 6,
+ }}
/>
);
}
Store the image
Now that we've chosen an image, we need to also store it when we save the plant. It's not sufficient to simply store the uri we get from the image picker, because that storage is not guaranteed to persist. We'll need to copy the image to the document storage for the app so it will exist as long as the app is installed.
For reading and writing files, we can use expo file system.
Install expo file system
npx expo install expo-file-system
Save the image when a new plant is added
First, let's pass the image uri to the addPlant
function:
Update: app/new.tsx
@@ -40,7 +40,7 @@ export default function NewScreen() {
);
}
- addPlant(name, Number(days));
+ addPlant(name, Number(days), imageUri);
router.navigate("/");
};
Now update the plants store to save the plant image along with the rest of the plant data:
Update: store/plantsStore.ts
@@ -1,18 +1,24 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
+import * as FileSystem from "expo-file-system";
export type PlantType = {
id: string;
name: string;
wateringFrequencyDays: number;
lastWateredAtTimestamp?: number;
+ imageUri?: string;
};
type PlantsState = {
nextId: number;
plants: PlantType[];
- addPlant: (name: string, wateringFrequencyDays: number) => void;
+ addPlant: (
+ name: string,
+ wateringFrequencyDays: number,
+ imageUri?: string,
+ ) => Promise<void>;
removePlant: (plantId: string) => void;
waterPlant: (plantId: string) => void;
};
@@ -22,7 +28,22 @@ export const usePlantStore = create(
(set) => ({
plants: [],
nextId: 1,
- addPlant: (name: string, wateringFrequencyDays: number) => {
+ addPlant: async (
+ name: string,
+ wateringFrequencyDays: number,
+ imageUri?: string,
+ ) => {
+ const savedImageUri =
+ FileSystem.documentDirectory +
+ `${new Date().getTime()}-${imageUri?.split("/").slice(-1)[0]}`;
+
+ if (imageUri) {
+ await FileSystem.copyAsync({
+ from: imageUri,
+ to: savedImageUri,
+ });
+ }
+
return set((state) => {
return {
...state,
@@ -32,6 +53,7 @@ export const usePlantStore = create(
id: String(state.nextId),
name,
wateringFrequencyDays,
+ imageUri: imageUri ? savedImageUri : undefined,
},
...state.plants,
],
Finally, update the PlantCard
to show the custom plant image if chosen:
Update: components/PlantCard.tsx
@@ -6,7 +6,7 @@ import { PlantlyImage } from "./PlantlyImage";
export function PlantCard({ plant }: { plant: PlantType }) {
return (
<View style={styles.plantCard}>
- <PlantlyImage size={100} />
+ <PlantlyImage size={100} imageUri={plant.imageUri} />
<View style={styles.details}>
<Text numberOfLines={1} style={styles.plantName}>
{plant.name}
Android | iOS |
---|---|