Last opp bilder med mobilkamera i firebase



Last opp bilder med mobilkamera i firebase

av Ole Petter den 2. august 2021

Sist oppdatert: 29 august, 2021 kl 12:08

Kildekode: https://github.com/olepetterkh91/ionic-camera-demo

Contents

Filer

For å bruke firebase må man konfigurere det til et prosjekt. Det er lurt å sette opp prosjektets ulike nøkler med en .env fil, som lagres i hovedmappen til prosjektet.

import firebase from 'firebase/app'
import 'firebase/firestore';
import 'firebase/auth'
import 'firebase/storage'

const firebaseConfig = {
    apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
    authDomain: process.env.REACT_APP_AUTH_DOMAIN,
    projectId: process.env.REACT_APP_PROJECT_ID,
    storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
    messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
    appId: process.env.REACT_APP_APP_ID,
    measurementId: process.env.REACT_APP_MEASUREMENT_ID
  
};

const app = firebase.initializeApp(firebaseConfig)
export const auth = app.auth();
export const firestore = app.firestore()
export const storage = app.storage()

export const timestamp = firebase.firestore.FieldValue.serverTimestamp;

For å laste opp bilder i firebase trenger man autentisering, for å registrere nye brukere brukes filen RegisterFirebase.tsx, og for innlogging, LoginFirebase.tsx.

For å logge inn, importeres auth fra firebase config filen firebase.ts. Hvis man logger inn, får man innloggingsinformasjon fra firebase authentication, som lagres i nettleseren eller enheten som bruker applikasjonen.

    const handleLogin = async () => {
        setLoading(true)
        try {
            const credential = await auth.signInWithEmailAndPassword(email, password)
            console.log('Credential:', credential)
            
        } catch (error) {
            setError(true)
            console.log(error)
        }
        setLoading(false)
    }

For å registrere en ny bruker brukes auth.createUserWithEmailAndPassword(), da vil brukere registreres i firebase ved registrering.

    const handleRegister = async () => {
        setLoading(true)
        try {
            const credential = await auth.createUserWithEmailAndPassword(email, password)
            console.log('Credential:', credential)
        } catch (error) {
            setError(true)
            console.log(error)
        }
        setLoading(false)
    }

AuthFirebase.tsx

import React, { useState, useContext } from 'react'
import { IonPage, IonHeader, IonToolbar, IonButtons, IonBackButton, IonTitle, IonContent, IonButton, IonText, IonRow, IonCol } from '@ionic/react'
import LoginFireBase from './LoginFirebase'
import RegisterFireBase from './RegisterFirebase'
import StateContext from '../../StateContext'

const AuthFirebase: React.FC = () => {

    const [activeItem, setActiveItem] = useState("1")
    const {loggedIn, userId} = useContext(StateContext)

    return (
        <IonPage>
            <IonHeader>
                <IonToolbar>
                    <IonButtons slot="start">
                        <IonBackButton  defaultHref="/" />
                    </IonButtons>
                    <IonTitle>{activeItem === "1" ? "Logg inn" : "Registrering"}</IonTitle>
                </IonToolbar>
            </IonHeader>
            <IonContent>

                {loggedIn && (
                    <IonRow>
                        <IonCol className="ion-text-center">
                            <IonText color="success"><p>Du logget inn</p></IonText>
                            <IonButton routerLink="/lokalfotball">Gå hjem</IonButton>
                        </IonCol>
                    </IonRow> 
                )}
                 
                {!loggedIn && <React.Fragment>
                    {activeItem === "1" && <LoginFireBase/>}
                    {activeItem === "2" && <RegisterFireBase />}

                    {activeItem === "1" && <IonButton fill="outline" expand="block" onClick={() => setActiveItem("2")}>Registrer</IonButton>}
                    {activeItem === "2" && <IonButton fill="outline" expand="full" onClick={() => setActiveItem("1")}>Logg inn</IonButton>}
                    <IonButton expand="block" color="danger" routerLink="/auth-firebase/reset-password">Glemt passord? Trykk her</IonButton>
                </React.Fragment>
                }

            </IonContent>
        </IonPage>
    )
}
export default AuthFirebase

RegisterFirebase.tsx

import React, { useState } from 'react'
import {auth} from '../../firebase'
import { IonList, IonItem, IonLabel, IonInput, IonButton, IonText, IonLoading } from '@ionic/react'

const RegisterFireBase: React.FC = () => {

    const [password, setPassword] = useState<any>("")
    const [email, setEmail] = useState<any>("")
    const [username, setUsername] = useState<any>("")
    const [error, setError] = useState(false)
    const [loading, setLoading] = useState(false)

    const handleRegister = async () => {
        setLoading(true)
        try {
            const credential = await auth.createUserWithEmailAndPassword(email, password)
            console.log('Credential:', credential)
        } catch (error) {
            setError(true)
            console.log(error)
        }
        setLoading(false)
    }

    return (
        <IonList>
            <IonItem>
                <IonLabel position="floating">Epost</IonLabel>
                <IonInput type="email" value={email} onIonChange={e => setEmail(e.detail.value)} />
            </IonItem>
            <IonItem>
                <IonLabel position="floating">Brukernavn</IonLabel>
                <IonInput value={username} onIonChange={e => setUsername(e.detail.value)} />
            </IonItem>
            <IonItem>
                <IonLabel position="floating">Passord</IonLabel>
                <IonInput type="password" value={password} onIonChange={e => setPassword(e.detail.value)} />
            </IonItem>

            {error && <IonText color="danger">Ugyldig passord eller epost</IonText>}
            
            <IonButton onClick={handleRegister} expand="block">Registrer</IonButton>
            <IonLoading isOpen={loading} />
        </IonList>
    )
}
export default RegisterFireBase

LoginFirebase.tsx

import React, { useState } from 'react'
import { IonList, IonItem, IonLabel, IonInput, IonButton, IonText, IonLoading } from '@ionic/react'
import { auth } from '../../firebase'

const LoginFireBase: React.FC = () => {

    const [password, setPassword] = useState<any>("")
    const [email, setEmail] = useState<any>("")
    const [error, setError] = useState(false)
    const [loading, setLoading] = useState(false)

    const handleLogin = async () => {
        setLoading(true)
        try {
            const credential = await auth.signInWithEmailAndPassword(email, password)
            console.log('Credential:', credential)
            
        } catch (error) {
            setError(true)
            console.log(error)
        }
        setLoading(false)
    }

    return (
        <IonList>
            <IonItem>
                <IonLabel position="floating">Epost</IonLabel>
                <IonInput value={email} onIonChange={e => setEmail(e.detail.value)} />
            </IonItem>
            <IonItem>
                <IonLabel position="floating">Passord</IonLabel>
                <IonInput type="password" value={password} onIonChange={e => setPassword(e.detail.value)} />
            </IonItem>
            {error && <IonText color="danger">Ugyldig passord eller epost</IonText>}
            <IonButton onClick={handleLogin} expand="block">Logg inn</IonButton>
            <IonLoading isOpen={loading} />
        </IonList>
    )
}
export default LoginFireBase

AddImage.tsx

For å laste opp bilder til firebase, brukes firebase storage, og man lager en referanse til mappen man vil lagre bildet med storage.ref(). Deretter kan man laste opp bildet til mappen med pictureRef.put(). Som respons får man et url man kan bruke til å vise bildet (await snapshot.ref.getDownloadURL();)

    async function savePicture(blobUrl: string) {
        let pictureRef;
        const date = new Date().toISOString();
        pictureRef = storage.ref("/images/" + date);

        const response = await fetch(blobUrl);
        const blob = await response.blob();
        const snapshot = await pictureRef.put(blob);
        const url = await snapshot.ref.getDownloadURL();
        return url;
    }

For å kommunisere med telefonens kamera, brukes biblioteket @capacitor/camera. Da kan man hente informasjon om bildet man tar med mobilen, og få et uri som kan brukes til å forhåndsvise bildet. Deretter kan man laste opp bildet fra mobilen til firebase.

    async function handlePictureClick() {
        if (isPlatform("capacitor")) {
            try {
                const photo: any = await Camera.getPhoto({
                    resultType: CameraResultType.Uri
                })
                setPictureUrl(photo.webPath)
            } catch (error) {
                console.log(error)
            }
        } else {
            fileInputRef?.current?.click()
        }
    }
import { IonButton, IonInput, IonItem, IonLabel, IonTextarea, IonToast, isPlatform } from "@ionic/react";
import { useContext, useRef, useState } from "react";
import { firestore, storage } from "../../../firebase"
import {Camera, CameraResultType} from "@capacitor/camera"
import StateContext from "../../../StateContext";

const AddImage: React.FC = () => {
    const {userId} = useContext(StateContext)
    const [pictureUrl, setPictureUrl] = useState("https://progitek.no/wp-content/uploads/2020/07/internet-technology-computer-display-1089440.jpg");
    const [title, setTitle] = useState<any>("");
    const [description, setDescription] = useState<any>("");
    const [message, setMessage] = useState("")
    const fileInputRef = useRef<HTMLInputElement>(null);

    function handleFileChange(event: any) {
        if (event.target.files.length > 0) {
            const file = event.target.files.item(0);
            const pictureUrl = URL.createObjectURL(file);
            setPictureUrl(pictureUrl);
        }
    }

    async function handlePictureClick() {
        if (isPlatform("capacitor")) {
            try {
                const photo: any = await Camera.getPhoto({
                    resultType: CameraResultType.Uri
                })
                setPictureUrl(photo.webPath)
            } catch (error) {
                console.log(error)
            }
        } else {
            fileInputRef?.current?.click()
        }
    }

    async function handleSave() {
        const imageUrl = await savePicture(pictureUrl);  
        console.log(imageUrl)  
        setPictureUrl(imageUrl)
    }

    async function savePicture(blobUrl: string) {
        let pictureRef;
        const date = new Date().toISOString();
        pictureRef = storage.ref("/images/" + date);

        const response = await fetch(blobUrl);
        const blob = await response.blob();
        const snapshot = await pictureRef.put(blob);
        const url = await snapshot.ref.getDownloadURL();
        saveImageToFirebase(url)
        return url;
    }

    async function saveImageToFirebase(url: string) {
        const imageRef = firestore.collection("images");
        try {
            const data = {
                imageUrl: url,
                user_id: userId,
                type: "gallery",
                date_added: new Date().toISOString(),
                title,
                description,
                username: "",
                avatar: ""
            }
            imageRef.add(data);
            setMessage("Bilde lagret!");
            setTimeout(() => {
                setMessage("");
            }, 2000)
        } catch (error) {
            console.log(error)
        }
    }

    return (
        <>
            <IonItem>
                <IonLabel position="stacked">
                    Picture (Click image to add new)
                </IonLabel>
                <br />
                <input
                    type="file"
                    accept="image/*"
                    onChange={handleFileChange}
                    ref={fileInputRef}
                    style={{ visibility: "hidden" }}
                />
                <img
                    src={pictureUrl}
                    style={{ cursor: "pointer" }}
                    alt=""
                    onClick={handlePictureClick}
                />
            </IonItem>
            <IonItem>
                <IonLabel position="floating">Tittel</IonLabel>
                <IonInput value={title} onIonChange={e => setTitle(e.detail.value)} />
            </IonItem>
            <IonItem>
                <IonLabel position="floating">Beskrivelse</IonLabel>
                <IonTextarea value={description} onIonChange={e => setDescription(e.detail.value)} />
            </IonItem>
            <IonButton expand="block" onClick={handleSave}>Save</IonButton>

            <IonToast isOpen={message ? true : false} message={message} onDidDismiss={() => setMessage("")} position="top" />
        </>
    )
}
export default AddImage

App.tsx

import { Redirect, Route, Switch } from 'react-router-dom';
import {
  IonApp,
  IonIcon,
  IonLabel,
  IonRouterOutlet,
  IonTabBar,
  IonTabButton,
  IonTabs,
} from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import { addSharp, homeSharp, personCircleSharp } from 'ionicons/icons';
import Tab1 from './pages/Tab1';
import Tab2 from './pages/Tab2';
import Tab3 from './pages/Tab3';
import { useImmerReducer } from "use-immer";
/* Core CSS required for Ionic components to work properly */
import '@ionic/react/css/core.css';

/* Basic CSS for apps built with Ionic */
import '@ionic/react/css/normalize.css';
import '@ionic/react/css/structure.css';
import '@ionic/react/css/typography.css';

/* Optional CSS utils that can be commented out */
import '@ionic/react/css/padding.css';
import '@ionic/react/css/float-elements.css';
import '@ionic/react/css/text-alignment.css';
import '@ionic/react/css/text-transformation.css';
import '@ionic/react/css/flex-utils.css';
import '@ionic/react/css/display.css';

/* Theme variables */
import './theme/variables.css';
import StateContext from './StateContext';
import DispatchContext from './DispatchContext';
import { auth } from './firebase';
import { useEffect } from 'react';

const App: React.FC = () => {

  const initialState = {
    loggedIn: false,
    userId: ""
  }

  function appReducer(draft: any, action: any) {
    switch (action.type) {
      case "loggedIn":
          draft.loggedIn = true;
          draft.userId = action.value;
          return;
      case "userFetched":
          draft.user = action.value;
          return;
      case "loggedOut":
          draft.loggedIn = false;
          draft.userId = "";
          return;
    }
  }

  const [state, dispatch] = useImmerReducer(appReducer, initialState);

  useEffect(() => {
    auth.onAuthStateChanged((user) => {
        if (user) {
            dispatch({ type: "loggedIn", value: user?.uid });
        }
    });
}, []);

  return (
    <IonApp>
      <StateContext.Provider value={state}>
        <DispatchContext.Provider value={dispatch}>
      <IonReactRouter>
        <IonTabs>
          <IonRouterOutlet>
            <Switch>
            <Route exact path="/tab1">
              <Tab1 />
            </Route>
            <Route exact path="/tab2">
              <Tab2 />
            </Route>
            <Route path="/tab3">
              <Tab3 />
            </Route>
            <Route exact path="/">
              <Redirect to="/tab1" />
            </Route>
            </Switch>
          </IonRouterOutlet>
          <IonTabBar slot="bottom">
            <IonTabButton tab="tab1" href="/tab1">
              <IonIcon icon={homeSharp} />
              <IonLabel>Tab 1</IonLabel>
            </IonTabButton>
            <IonTabButton tab="tab2" href="/tab2">
              <IonIcon icon={addSharp} />
              <IonLabel>Tab 2</IonLabel>
            </IonTabButton>
            <IonTabButton tab="tab3" href="/tab3">
              <IonIcon icon={personCircleSharp} />
              <IonLabel>Tab 3</IonLabel>
            </IonTabButton>
          </IonTabBar>
        </IonTabs>
      </IonReactRouter>
      </DispatchContext.Provider>
      </StateContext.Provider>
    </IonApp>
  );
} 

export default App;

Images.tsx

import { IonCol, IonGrid, IonRow } from "@ionic/react"
import { useEffect, useState } from "react"
import { firestore } from "../../firebase";
import ImageGridListTemplate from "./templates/ImageGridListTemplate";

const Images: React.FC = () => {

    const [images, setImages] = useState<any>([]);

    useEffect(() => {
        const imagesRef = firestore.collection("images");
        imagesRef.orderBy("date_added", "desc").onSnapshot(({docs}) => {
            const imagesFB = docs.map(doc => {
                return {id: doc.id, ...doc.data()}
            })
            setImages(imagesFB);
        })
    }, [])

    return (
        <IonGrid>
            <IonRow>
                <IonCol sizeLg="8" sizeXl="6" offsetLg="2" offsetXl="3">
                    {images?.map((image: any, index: number) => <ImageGridListTemplate key={index} image={image} />)}
                </IonCol>
            </IonRow>
        </IonGrid>
    )
}
export default Images

ImageGridListTemplate.tsx

import { IonAvatar, IonButton, IonButtons, IonCard, IonIcon, IonImg, IonItem, IonLabel } from "@ionic/react"
import { bookmark, bookmarkOutline, chatbubble, chatbubbleOutline, ellipsisHorizontal, heart, heartOutline, paperPlaneOutline, paperPlaneSharp } from "ionicons/icons"
import React from "react"

const ImageGridListTemplate: React.FC<{
    image: any
}> = ({image}) => {
    return (
        <React.Fragment>
            <IonItem lines="full">
                <IonAvatar slot="start">
                    <IonImg src={image?.avatar || ""} />
                </IonAvatar>
                <IonLabel>Test</IonLabel>
                <IonButtons slot="end">
                    <IonButton>
                        <IonIcon slot="icon-only" icon={ellipsisHorizontal} />
                    </IonButton>
                </IonButtons>
            </IonItem>
            <IonImg src={image?.imageUrl} alt="" />
            <IonItem lines="none">
                <IonButtons slot="start">
                    <IonButton>
                        <IonIcon slot="icon-only" icon={heartOutline} />
                    </IonButton>
                    <IonButton>
                        <IonIcon slot="icon-only" icon={chatbubbleOutline} />
                    </IonButton>
                    <IonButton>
                        <IonIcon slot="icon-only" icon={paperPlaneOutline} />
                    </IonButton>
                </IonButtons>
                <IonButtons slot="end">
                    <IonButton>
                        <IonIcon slot="icon-only" icon={bookmarkOutline} />
                    </IonButton>
                </IonButtons>
            </IonItem>
            <IonItem lines="none">
                <IonLabel slot="start">
                    <p><strong>76 liker</strong></p>
                    <p><strong>{image?.username}</strong> {image?.description}</p>
                </IonLabel>
                <IonLabel slot="start"></IonLabel>
            </IonItem>
        </React.Fragment>
    )
}
export default ImageGridListTemplate

.env

REACT_APP_FIREBASE_API_KEY=YOUR_FIREBASE_API_KEY
REACT_APP_AUTH_DOMAIN=YOUR_FIREBASE_AUTH_DOMAIN
REACT_APP_PROJECT_ID=YOUR_FIREBASE_PROJECT_ID
REACT_APP_STORAGE_BUCKET=YOUR_FIREBASE_STORAGE_BUCKET
REACT_APP_MESSAGING_SENDER_ID=YOUR_FIREBASE_SENDER_ID
REACT_APP_APP_ID=YOUR_FIREBASE_APP_ID
REACT_APP_MEASUREMENT_ID=YOUR_FIREBASE_MEASUREMENT_ID
Legg igjen en kommentar