DiscoverPlaces Raport #10 – dodawanie wiadomości, wybór zdjęcia oraz wideo
Pora na kolejny raport odnośnie tworzenia projektu DiscoverPlaces, w których przedstawione zostanie tworzenie komponentu MessageCreator do tworzenia nowych wiadomości oraz komponentów pomocniczych do załączania zdjęć oraz wideo. Opisane zostaną również problemy wynikłe w czasie pracy.
Dodawanie wiadomości ze zdjęciem lub filmem
Według założeń projektu zarówno w wiadomości jak i w komentarzu ma być możliwość załączenia zdjęcia lub wideo. Dlatego w myśl zasady DRY wygodnie będzie utworzyć dwa osobne komponenty, które następnie będzie można wykorzystać podczas tworzenia wiadomości oraz komentarza.
Wspomogłem się tutaj paczką React Native Image Picker. Jej instalacja jest bardzo prosta, jedyne co trzeba zrobić ręcznie to wstawić odpowiednie uprawnienia, ale dokumentacja doskonale to opisuje. Poniżej wstawione zostały dwa komponenty odpowiedzialne za wybór zdjęcia oraz wideo:
import React, { Component } from 'react'; import { Button } from 'react-native-elements'; import ImagePicker from 'react-native-image-picker'; export default class PhotoPicker extends Component { constructor(props) { super(props); this.state = { onPick: props.onPick }; } selectPhoto() { const options = { quality: 1.0, storageOptions: { skipBackup: true } }; ImagePicker.showImagePicker(options, (response) => { console.log('Response = ', response); if (response.didCancel) { console.log('User cancelled photo picker'); } else if (response.error) { console.log('ImagePicker Error: ', response.error); } else if (response.customButton) { console.log('User tapped custom button: ', response.customButton); } else { const source = { uri: response.uri, name: response.fileName, type: response.type }; this.props.onPick('photo', source); } }); } render () { return ( <Button buttonStyle={{marginTop: 10, marginBottom: 5}} title="Add photo" icon={{name: 'camera', type: 'font-awesome'}} backgroundColor="blue" onPress={() => this.selectPhoto()} /> ); } }
import React, { Component } from 'react'; import { Button } from 'react-native-elements'; import ImagePicker from 'react-native-image-picker'; export default class PhotoPicker extends Component { constructor(props) { super(props); this.state = { onPick: props.onPick }; } selectVideo () { const options = { title: 'Video Picker', takePhotoButtonTitle: 'Take Video...', mediaType: 'video', videoQuality: 'medium' }; ImagePicker.showImagePicker(options, (response) => { console.log('Response = ', response); if (response.didCancel) { console.log('User cancelled video picker'); } else if (response.error) { console.log('ImagePicker Error: ', response.error); } else if (response.customButton) { console.log('User tapped custom button: ', response.customButton); } else { const source = { uri: response.uri, name: 'video.mp4', type: 'video/mp4' }; this.props.onPick('video', source); } }); } render () { return ( <Button buttonStyle={{marginTop: 5, marginBottom: 10}} title="Add video" icon={{name: 'video-camera', type: 'font-awesome'}} backgroundColor="brown" onPress={() => this.selectVideo()} /> ); } }
Komponent MessageCreator wygląda w ten sposób:
import React, { Component } from 'react'; import { View, Picker, Text } from 'react-native'; import { FormLabel, FormInput, Button } from 'react-native-elements'; import { createMessage } from './../../config/api'; import update from 'immutability-helper'; import PhotoPicker from './../Utils/PhotoPicker'; import VideoPicker from './../Utils/VideoPicker'; const AVAILABLE_SCOPES = [1,2,5]; const HTTP_CREATED = 201; export default class Creator extends Component { constructor (props) { super(props); this.state = { valid: false, message: { content: null, photo: null, video: null, latitude: 0, longitude: 0, scope: 1 } }; } updateField (property, value) { if (!this.state.message.hasOwnProperty(property)) { return; } const message = update(this.state.message, {[property]: { $set: value }}); this.setState({ message: message }); this.validate(); } handlePick (target, source) { const message = update(this.state.message, {[target]: { $set: source }}); this.setState({ message: message }); this.validate(); } submit () { this.validate(); if (!this.state.valid) { return false; } navigator.geolocation.getCurrentPosition(position => { let message = this.state.message; message.latitude = position.coords.latitude; message.longitude = position.coords.longitude; createMessage(message) .then(response => { if (response.status === HTTP_CREATED) { this.props.navigation.navigate('MessageList'); } }); }); } validate () { if ( ( (this.state.message.content && this.state.message.content !== '') || this.state.message.photo || this.state.message.video ) && AVAILABLE_SCOPES.includes(this.state.message.scope) ) { this.setState({valid: true}); } else { this.setState({valid: false}); } } render () { return ( <View> {!this.state.valid && <Text style={{ color: 'red', marginTop: 10, marginBottom: 10, textAlign: 'center' }} > You have to add content, photo or video </Text> } <FormLabel>Content</FormLabel> <FormInput onChangeText={(content) => this.updateField('content', content)} value={this.state.message.content} /> <PhotoPicker onPick={this.handlePick.bind(this)} /> <VideoPicker onPick={this.handlePick.bind(this)} /> <Picker selectedValue={this.state.message.scope.toString()} onValueChange={(scope) => this.updateField('scope', parseInt(scope))} > <Picker.Item label="1km" value="1" /> <Picker.Item label="2km" value="2" /> <Picker.Item label="5km" value="5" /> </Picker> <Button buttonStyle={{marginTop: 10, marginBottom: 10}} title="Add" icon={{name: 'plus', type: 'font-awesome'}} backgroundColor="green" large={true} onPress={this.submit.bind(this)} /> </View> ); } }
Całość kodu można znaleźć na githubie.
Napotkane problemy
React Native Image Picker
Pierwszym problemem, na który się napotkałem to niedziałające nagrywanie wideo na Androidzie 7.0, winą obarczam paczkę, której używam. Póki co przerzuciłem się na testowanie na emulatorze z androidem 6.0, później pomyślę jak to rozwiązać.
Eslint
Eslint zgłasza błędy w przypadku wykorzystywania niezdefiniowanych zmiennych, niestety nie wykrywa takich elementów jak FormData czy navigator z React Native, więc zaktualizowałem config dodając:
"globals": { "FormData": false, "navigator": false }
React Native – Network request failed
Gdy przesyłamy pliki należy dodać również name oraz type, w przypadku obiektu wyglądającego w ten sposób:
let source = {uri: response.uri};
React Native wyrzuca błąd „Network request failed”. Powinno to wyglądać następująco:
const source = { uri: response.uri, name: response.fileName, type: response.type };
Symfony – Unable to find template
Ten problem był o tyle ciekawy, że gdy przesyłałem dane jako form-data przez Postmana, wszystko działało jak należy, natomiast z poziomu kodu przesyłając FormData, Symfony wyrzuca warning Unable to find template, tak jakby chciało odpowiedzieć htmlem poprzez twiga. Na pierwszy rzut oka Requesty z Postmana i kodu niczym się nie różniły. Nie sprawdzałem jak wyglądają Requesty docierające do serwera, możliwe, że Postman dodaje sam jakieś nagłówki i tego nie wyświetla. Sprawę rozwiązałem dodając do kodu nagłówek 'Accept’: 'application/json’.
Devtools
Dla React Native powinny działać te same narzędzia co dla samego Reacta, czyli rozszerzenie do chrome oraz zewnętrzne dev-tools. Jednak w moim przypadku w przeglądarce jedyne co mogę to przechwycić console.log(), natomiast gdy przełączę na osobne dev-tools, to co prawda pokazuje mi cały Virtual Dom, aczkolwiek nie przechwytuje console.log(). Póki co szukałem jedynie na szybko jakichś informacji, nie miałem czasu na dokładniejsze sprawdzanie. Postaram się to jakoś rozwiązać i pewnie w kolejnym wpisie dam znać czy się udało.
Subscribe and master unit testing with my FREE eBook (+60 pages)! 🚀
In these times, the benefits of writing unit tests are huge. I think that most of the recently started projects contain unit tests. In enterprise applications with a lot of business logic, unit tests are the most important tests, because they are fast and can us instantly assure that our implementation is correct. However, I often see a problem with good tests in projects, though these tests’ benefits are only huge when you have good unit tests. So in this ebook, I share many tips on what to do to write good unit tests.
Dodaj komentarz