Component Life Cycle

životný cyklus komponentov reprezentovaných triedami a funkciami, povolenia aplikácie v OS Android

Záznam z prednášky

Adverts and Annoucements

Tool Genymotion

  • emulátor postavený na projekte Android-x86
  • spúšťaný pomocou VirtualBox-u
  • ľahší manažment, nie je až taký žrút ako oficiálny emulátor
  • nemá však všetky vlastnosti pôvodného emulátoru
    • napr. NFC
    • zrejme len v neplatenej verzii

Tool scrcpy

Introduction

  • (slide) stále pracujeme na baterke

    • baterka svieti
    • Dokonca vieme aj v prípade, že zariadenie baterkou nedisponuje, zobraziť správu a aplikáciu vypnúť. To však robíme neskoro - až vtedy, keď príde požiadavka na zasvietenie/zhasnutie.
    • Dnes vykonáme túto kontrolu rovno pri spustení aplikácie.

Component Lifecycle

  • (slide) Keďže je rámec React Native založený na rámci React, jeho komponenty sa riadia životným cyklom komponentov React-u. Tento životný cyklus je reprezentovaný niekoľkými metódami, ktoré je možné v našej implementácii prepísať (override).

  • (slide) Životný cyklus je možné ilustrovať nasledovným diagramom, ktorý nám poslúži ako istý ťahák:

    React Component Lifecycle [@y13]
  • Aby sme mu lepšie rozumeli, potrebujeme porozumieť nasledovným termínom:

    • mounting - proces pripojenia komponentu do DOM-u
    • updating - proces aktualizácie vlastností komponentu
    • unmounting - proces odpojenia komponentu z DOM-u

Component Lifecycle in Class Components

  • V prípade reprezentácie komponentov pomocou tried vieme do tohto procesu vstúpiť prepísaním metód

    • constructor() - Konštruktor komponentu je volaný ešte predtým, ako je komponent pripojený. Obyčajne sa konštruktor používa z dvoch dôvodov:

      1. na inicializáciu lokálneho stavu priradením objektu do this.state.

      2. na prihlásenie metód v prípade vzniku udalosti

    • componentDidMount() - Metóda je volaná okamžite po pripojení do DOM-u. Používa sa na inicializáciu, pri ktorej sa vyžaduje existencia uzlov v DOM-e. Metóda je rovnako dobrým miestom, ak potrebujete získať údaje zo vzdialenej služby. Môžete sa v nej tiež prihlásiť na odber udalostí. V tom prípade sa z nich nezabudnite odhlásiť v metóde componentWillUnmount().

    • componentDidUpdate() - Metóda je zavolaná okamžite po aktualizácii komponentu. Nie je však zavolaná po prvom renderovaní (volaní render()). Táto metóda sa používa v prípadoch, ktoré súvisia s aktualizovaním komponentu.

    • componentWillUnmount() - Je zavolaná tesne predtým, ako je komponent odpojený a odstránený. Funkcia sa používa na činnosti súvisiace s cleanup-om pri odstraňovaní komponentu, ako rušenie časovačov, ukončenie sieťových operácií alebo odhlásenie sa z odberu udalostí, ku ktorým sa komponent prihlásil v metóde componentDidMount().

  • Metód, ktoré sa používajú v procese životného cyklu komponentu je síce viac, ale tie ostatné sa používajú v špeciálnych prípadoch. Tieto uvedené sú najčastejšie používanými.

Clock as Class Component Example

  • Aby sme lepšie porozumeli tomu, ako životný cyklus komponentu funguje, ukážeme si ho na jednoduchom príklade komponentu reprezentujúcom hodiny. Jeho kód bude vyzerať nasledovne:

    import React, { Component } from "react";
    import { StyleSheet, View, Text } from "react-native";
    
    export default class App extends Component {
        constructor(props) {
            super(props);
    
            console.log();
            console.log(">> constructor");
            this.state = {
                now: new Date(),
            };
        }
    
        render() {
            console.log(">> render()");
    
            return (
                <View style={styles.container}>
                    <Text style={styles.clock}>
                        {this.state.now.toLocaleTimeString()}
                    </Text>
                </View>
            );
        }
    
        componentDidMount() {
           console.log(">> componentDidMount()");
    
           this.timerId = setInterval(() => this.tick(), 1000 * 1);
        }
    
        tick() {
            console.log(">> tick");
            this.setState({
                now: new Date(),
            });
        }
    
        componentDidUpdate() {
            console.log(">> componentDidUpdate()");
        }
    
        componentWillUnmount() {
            console.log(">> componentWillUnmount()");
            clearInterval(this.timerId);
        }
    }
    
    const styles = StyleSheet.create({
        container: {
            flex: 1,
            backgroundColor: "#fff",
            alignItems: "center",
            justifyContent: "center",
        },
        clock: {
            fontSize: 40,
        },
    });

Component Lifecycle in Functional Components

  • (slide) Situácia v prípade reprezentácie komponentu pomocou funkcie je však iná. Tu totiž všetky uvedené funkcie nahradíme pomocou jedného hook-u s názvom useEffect()

    import { useEffect } from "react";
    
    useEffect(function() {
        // code to run
    });
  • V závislosti od zápisu tohto hook-u dôjde k použitiu, ako sme videli v prípade komponentov reprezentovaných ako triedy. Na jednotlivé možnosti sa pozrieme bližšie.

Run Once

  • (slide) Tento spôsob použitia je podobný použitiu metódy componentDidMount()

  • Funkcia dostane v tomto prípade prázdny zoznam ako druhý parameter:

    useEffect(function(){
      // code to run
    }, []);

Run on Props Change

  • (slide) Tento spôsob použitia je podobný použitiu metódy componentDidUpdate()

  • Komponent dostane v tomto prípade ako parameter props. Tie sa stanú parametrom funkcie useEffect(). Samozrejme - nemusí sa jednať len o jeden z nich, ale je možné ich vymenovať viac.

  • Použitie hook-u je nasledovné:

    function Component({someProp}){
        useEffect(function(){
            // code to run
        }, [someProp]);
    }

Run on State Change

  • (slide) Tento spôsob použitia je podobný použitiu metódy componentDidUpdate()

  • Parametrom hook-u je v tomto prípade premenná reprezentujúca stav. Pri jej zmene je zavolaný kód funkcie. Samozrejme - nemusí sa jednať len o jeden z nich, ale je možné ich vymenovať viac.

  • Použitie hook-u je nasledovné:

    function Component(){
        const [state, setState] = useState();
        useEffect(function(){
            // code to run
        }, [state]);
    }

Run After Every Render

  • (slide) Tento spôsob použitia je podobný použitiu metódy componentDidUpdate()

  • V tomto prípade sa hook spustí po každom aktualizovaní, resp. vykreslení komponentu.

  • Použitie hook-u je nasledovné:

    useEffect(function(){
        // code to run
    });

Run on Unmount

  • (slide) Tento spôsob použitia je podobný použitiu metódy componentWillUnmount()

  • V tomto prípade hook vráti funkciu. Jej kód sa spustí vtedy, keď dôjde k zrušeniu komponentu.

  • Použitie hook-u je nasledovné:

    useEffect(function(){
        return function(){
            // code to run
        };
    });

Clock as Functional Component Example

  • Výsledná implementácia komponentu hodín, ktoré budú reprezentované pomocou funkcie, je nasledovná:

    import React, { useState, useEffect } from "react";
    import { StyleSheet, View, Text } from "react-native";
    
    export default function App() {
        const [now, setNow] = useState(new Date());
    
        useEffect(function () {
            console.log(">> componentDidMount()");
            const intervalId = setInterval(function () {
                console.log(">> tick");
                setNow(new Date());
            }, 1000 * 1);
    
            return function () {
                console.log(">> componentWillUnmount()");
                clearInterval(intervalId);
            };
        }, []);
    
        useEffect(
            function () {
                console.log(">> componentDidUpdate()");
            },
            [date]
        );
    
        return (
            <View style={styles.container}>
                <Text style={styles.clock}>
                    {now.toLocaleTimeString()}
                </Text>
            </View>
        );
    }
    
    const styles = StyleSheet.create({
        container: {
            flex: 1,
            backgroundColor: "#fff",
            alignItems: "center",
            justifyContent: "center",
        },
        clock: {
            fontSize: 40,
        },
    });

Checking the Presence of Flashlight

  • V našom prípade budeme teda potrebovať zabezpečiť, aby sa prítomnosť blesku overila pri spustení aplikácie - pri zavedení (mount) komponentu. Takže potrebujeme, aby sa konkrétny kód vykonal len raz po spustení. Použijeme teda hook useEffect() v roli metódy componentDidMount():

    useEffect(function () {
        Torch.switchState(isOn).catch(function (e) {
            Alert.alert(
                "Missing Flashlight",
                "No camera available. Go and buy a device " +
                "with some (or two) and come back later.",
                [
                    {
                        text: "Quit",
                        onPress: function () {
                            RNExitApp.exitApp();
                        },
                    },
                ]
            );
        });
    }, []);
  • Ostatný kód z funkcie toggle(), ktorý bol doteraz zodpovedný za prepínanie stavu baterky, môžeme vyhodiť a funkcia môže zostať v pôvodnom tvare:

    const toggleState = async function () {
        await Torch.switchState(!isOn);
        setIsOn(!isOn);
    };
  • Po spustení aplikácie v emulátore sa nám hneď zobrazí dialógové okno, kde po kliknutí na tlačidlo Quit sa aplikácia vypne. Ak však aplikáciu spustíme na reálnom zariadení, ktoré je bleskom vybavené, bude aplikácia pracovať správne.

Android Permissions

  • (slide) Je tu však ešte jeden problém, ktorý je potrebné vyriešiť na platforme Android. S príchodom verzie 7 sa totiž zmenili možnosti používania práv/povolení aplikáciami. Do tejto verzie 6 sa povolenia pre aplikáciu povoľovali len raz a to pri inštalácii. Aby ste aplikáciu mohli nainštalovať, museli ste povoliť všetko. Ináč ste si aplikáciu nainštalovať nemohli.

  • Od verzie 7 sa však tento prístup zmenil a aplikáciu nainštalujete bez toho, aby ste čokoľvek povoľovali. Android totiž umožňuje zapínať a vypínať povolenia aplikácie selektívne počas jej behu. To pre nás znamená, že síce test na prítomnosť blesku nám zbehne v poriadku, ale nemôžeme si byť istý, či je prístup k blesku pre aplikáciu povolený na úrovni operačného systému.

  • Preto musíme aplikáciu aktualizovať a pri každom prístupe k blesku vo funkcii toggle() najprv overiť, či na platforme Android príslušné povolenia máme alebo nie.

  • Test platformy je jednoduchý - pomocou triedy Platform sa vieme opýtať na bežiaci operačný systém a v prípade Android-u urobíme overenie:

    const toggleState = async function () {
        if(Platform.OS === 'android'){
            console.info('>> checking permissions first')
        }
    
        await Torch.switchState(!isOn);
        setIsOn(!isOn);
    };

Checking Permissions with Module react-native-torch

  • Modul react-native-torch, ktorý používame na svietenie baterkou, túto kontrolu už obsahuje, takže sa inšpirujeme ukážkou kódu, ktorú má na stránke a jemne ju upravíme pre naše použitie:

    const toggleState = async function () {
        let cameraAllowed = true;
    
        if (Platform.OS === "android") {
            cameraAllowed = await Torch.requestCameraPermission(
                "Camera Permissions",
                "We require camera permissions to use the " +
                "torch on the back of your phone."
            );
        }
    
        if (cameraAllowed) {
            await Torch.switchState(!isOn);
            setIsOn(!isOn);
        }
    };
  • Ak teraz aplikáciu spustíme prvýkrát a klikneme buď na obrázok alebo na tlačidlo, systém Android si od nás vypýta explicitne povolenie pre prístup ku kamere. Ak aplikácii povolenie nedáme, pri ďalšom kliknutí sa nás bude pýtať znova. Ak naopak povolenie aplikácii udelíme, bude aplikácia pekne svietiť.

  • Overiť, poprípade zmeniť povolenia aplikácie môžeme aj ručne v systéme Android. To môžeme zabezpečiť cez Settings > Applications > Torch > Permissions.

Checking Permissions Manualy

  • V tomto prípade sme mali k dispozícii rovno volanie, ktoré poskytovalo API daného modulu. Čo však v prípade, že takúto možnosť nemáme?

  • React Native vo svojom API obsahuje objekt PermissionsAndroid, pomocou ktorého je možné overiť ktorékoľvek povolenie systému Android. Používa na to funkciu .request(), ktorej povinným parametrom je je požadované povolenie. Toto povolenie je dostupné v tomto objekte ako konštanta cez PermissionsAndroid.PERMISSION. Napríklad povolenie pre prístup ku kamere je dostupné ako konštanta PermissionsAndroid.PERMISSIONS.CAMERA. Komplentý zoznam povolení je možné nájsť v dokumentácii.

  • Funkcia vráti výsledok, ktorý môže byť buď

    • PermissionsAndroid.RESULTS.GRANTED - povolené
    • PermissionsAndroid.RESULTS.DENIED - zakázané
    • PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN - zakázané a už sa viac nepýtať
  • Upravíme teda implementáciu funkcie toggleState() nasledovne:

    const toggleState = async function () {
        let cameraAllowed = true;
    
        if (Platform.OS === "android") {
            const granted = await PermissionsAndroid.request(
                PermissionsAndroid.PERMISSIONS.CAMERA
            );
    
            cameraAllowed = 
                (granted === PermissionsAndroid.RESULTS.GRANTED);
        }
    
        if (cameraAllowed) {
            await Torch.switchState(!isOn);
            setIsOn(!isOn);
        }
    };
  • Druhým nepovinným parametrom funkcie .request() je tzv. rationale. V prípade, že bude uvedený, tak predtým, ako sa zobrazí samotný systémový dialóg s povolením/zakázaním príslušného povolenia, sa zobrazí pomocný dialóg s dodatočnými informáciami napr. s vysvetlením použitia daného povolenia:

    const toggleState = async function () {
        let cameraAllowed = true;
    
        if (Platform.OS === "android") {
            const granted = await PermissionsAndroid.request(
                PermissionsAndroid.PERMISSIONS.CAMERA,
                {
                    title: "Torch Needs Camera Permission",
                    message:
                        "Torch app uses camera flashlight as " +
                        "torch. To make Torch work, you need " +
                        "to allow Camera Permission.",
                    buttonNeutral: "Ask Me Later",
                    buttonNegative: "Cancel",
                    buttonPositive: "OK",
                }
            );
    
            cameraAllowed = 
                granted === PermissionsAndroid.RESULTS.GRANTED;
        }
    
        if (cameraAllowed) {
            await Torch.switchState(!isOn);
            setIsOn(!isOn);
        }
    };

Conclusion

  • Dnes sme sa teda pozreli na to, ako vieme ovplyvniť správanie komponentu vzhľadom na jeho životný cyklus a ako na platforme Android zabezpečiť potrebné prístupové práva v prípade špeciálnej funkcionality.

  • Nabudúce sa pozrieme na to, ako aplikácie prekladať do iných jazykov a lepšie zorganizujeme projekt.