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
- This application provides display and control of Android devices connected on USB (or over TCP/IP). It does not require any root access. It works on GNU/Linux, Windows and macOS.
- How to Mirror & Control Your Android Phone from the Ubuntu Desktop
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:
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:na inicializáciu lokálneho stavu priradením objektu do
this.state
.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ódecomponentWillUnmount()
.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ódecomponentDidMount()
.
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 funkcieuseEffect()
. 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}> .toLocaleTimeString()} {now</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ódycomponentDidMount()
:useEffect(function () { .switchState(isOn).catch(function (e) { Torch.alert( Alert"Missing Flashlight", "No camera available. Go and buy a device " + "with some (or two) and come back later.", [ {text: "Quit", onPress: function () { .exitApp(); RNExitApp, }, } ]; ); }), []); }
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") { = await Torch.requestCameraPermission( cameraAllowed "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 cezPermissionsAndroid.PERMISSION
. Napríklad povolenie pre prístup ku kamere je dostupné ako konštantaPermissionsAndroid.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( .PERMISSIONS.CAMERA PermissionsAndroid; ) = cameraAllowed === PermissionsAndroid.RESULTS.GRANTED); (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( .PERMISSIONS.CAMERA, PermissionsAndroid {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 === PermissionsAndroid.RESULTS.GRANTED; 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.