Sunday 23 December 2018

Canceling Promise in Javascript

Canceling Promise in Js is a often sough after functionality, that can be provided by wrapping the Promise async function and provide canceling abilities. This will in functionality be similar to what we can do in C# with a CancellationTokenSource using in System.Threading.Task objects. We can invoke asynchronous function in Js with Promise, but if the user navigates away from a View or Page in for example React Native component, clicking a button to go to another Component, we must tidy up already started Promise operations such as fetch and here is the code to achieve that. First off, we define and export a makeCancelable method to be able to cancel a Promise.

/**
 * Wraps a promise into a cancelable promise, allowing it to be canceled. Useful in scenarios such as navigating away from a view or page and a fetch is already started.
 * @param {Promise}   promise           Promise object to cancel.
 * @return {Object with wrapped promise and a cancel function}
 */
export const makeCancelable = (promise) => {
    let hasCanceled = false;

    const wrappedPromise = new Promise((resolve, reject) => {
        promise.then(value => hasCanceled ? reject({ isCanceled: true }) : resolve(value),
            error => hasCanceled ? reject({ isCanceled: true }) : reject(error)
        );
    });

    return {
        promise: wrappedPromise,
        cancel() {
            hasCanceled: true;
        }
    };

};

The promise is wrapped with additional logic to check a boolean flag in a variable hasCanceled that either rejects the Promise if it is canceled or resolves the Promise (fullfils the async operation). Returned is an object in Js with the Promise itself in a primise attribute and the function cancel() which sets the boolean flag hasCanceled to true, effectively rejecting the Promise and rejecting it. Example usage below:

'use strict';

import React, { Component } from 'react';
import { TextInput, Text, View, StyleSheet, Image, TouchableHighlight, ActivityIndicator, FlatList, AsyncStorage } from 'react-native';
import AuthService from './AuthService';
import { makeCancelable } from './Util';

const styles = StyleSheet.create({
    container: {
        backgroundColor: '#F5FCFF',
        flex: 1,
        paddingTop: 40,
        alignItems: 'center'
    },
    heading: {
        fontSize: 30,
        fontWeight: '100',
        marginTop: 20
    },
    input: {
        height: 50,
        marginTop: 10,
        padding: 4,
        margin: 2,
        alignSelf: 'stretch',
        fontSize: 18,
        borderWidth: 1,
        borderColor: '#48bbec'
    },
    button: {
        height: 50,
        backgroundColor: '#48bbec',
        alignSelf: 'stretch',
        marginTop: 10,
        justifyContent: 'center'
    },
    buttonText: {
        fontSize: 22,
        color: '#FFF',
        alignSelf: 'center'
    },
    error: {
        fontWeight: '300',
        fontSize: 20,
        color: 'red',
        paddingTop: 10
    }
});

const cancelableSearchRepositoriesPromiseFetch = makeCancelable(fetch('https://api.github.com/search/repositories?q=react'));

class LoginForm extends Component {

    constructor(props) {
        super(props);

        this.state = {
            showProgress: false,
            username: '',
            password: '',
            repos: [],
            badCredentials: false,
            unknownError: false,
        };


    }

    onLoginPressed() {
        this.setState({ showProgress: true });

        var reposFound = [];

        var authService = new AuthService();

        authService.login({
            username: this.state.username, password: this.state.password
        }, (results) => {
            this.setState(Object.assign({ showProgress: false }, results));

            if (this.state.success && this.props.onLogin) {
                this.props.onLogin();
            }

        });




        cancelableSearchRepositoriesPromiseFetch.promise.then((response) => { return response.json(); })
            .then((results) => {
                results.items.forEach(item => {
                    reposFound.push(item);
                });
                this.setState({ repos: reposFound, showProgress: false });
            });
    }

    componentWillMount() {
        this._retrieveLastCredentials();
        cancelableSearchRepositoriesPromiseFetch.cancel();
    }

    _retrieveLastCredentials = async () => {

        var lastusername = await AsyncStorage.getItem("GithubDemo:LastUsername");
        var lastpassword = await AsyncStorage.getItem("GithubDemo:LastPassword");
        this.setState({ username: lastusername, password: lastpassword });
    }

    _saveLastUsername = async (username) => {
        if (username != null) {
            await AsyncStorage.setItem("GithubDemo:LastUsername", username);
        }
    }

    _savePassword = async (password) => {
        if (password != null) {
            await AsyncStorage.setItem("GithubDemo:LastPassword", password);
        }
    }

    componentWillUnmount() {

    }

    render() {


        var errorCtrl = <View />;

        if (!this.state.success && this.state.badCredentials) {
            errorCtrl = <Text color='#FF0000' style={styles.error}>That username and password combination did not work</Text>
        }

        if (!this.state.success && this.state.unknownError) {
            errorCtrl = <Text color='#FF0000' style={styles.error}>Unexpected error while logging in. Try again later</Text>
        }

        return (
            <View style={styles.container}>
                <Image style={{ width: 66, height: 55 }} source={require('./assets/Octocat.png')} />
                <Text style={styles.heading}>Github browser</Text>
                <TextInput value={this.state.username} onChangeText={(text) => { this._saveLastUsername(text); this.setState({ username: text }); }} style={styles.input} placeholder='Github username' />
                <TextInput value={this.state.password} textContentType={'password'} multiline={false} secureTextEntry={true} onChangeText={(text) => { this._savePassword(text); this.setState({ password: text }); }} style={styles.input} placeholder='Github password' />
                <TouchableHighlight style={styles.button} onPress={this.onLoginPressed.bind(this)}>
                    <Text style={styles.buttonText}>Log in</Text>
                </TouchableHighlight>
                {errorCtrl}
                <ActivityIndicator animating={this.state.showProgress} size={"large"} />
                <FlatList keyExtractor={item => item.id} data={this.state.repos} renderItem={({ item }) => <Text>{item.full_name}</Text>} />

            </View>
        );

    }


}

export default LoginForm; 


Thursday 20 December 2018

React native - Checkboxes and date pickers for IoS

This article will look at checkboxes and date pickers for IoS apps in React Native. For checkboxes, we use the built-in Switch control. For Date pickers we use the built-in DatePickerIOS control.
This is the basic JS / JSX to get you started! Note the use of a basic state variable to keep track of the chosenDate. The onDateChange callback uses an arrow inline function to update the state variable. We also set the minimum and maximum datetime allowed and some othern nice props of the control! App.js

import React from 'react';
import { StyleSheet, Text, View, DatePickerIOS, Switch } from 'react-native';

export default class App extends React.Component {

  constructor(props){
    super(props);
    this.state = { chosenDate : new Date(), isDatePickerVisible: false };

    this.setDate = this.setDate.bind(this);
  }

  setDate(newDate){
    this.setState({chosenDate: newDate })
  }


  render() {
    return (
      <View style={styles.container}>
 
        <Switch onValueChange={(val) => this.setState({ isDatePickerVisible : val })} value={this.state.isDatePickerVisible} ios_backgroundColor={"aliceblue"} trackColor={true} />
        <Text style={{ display: this.state.isDatePickerVisible ? "none" : "flex"}}>Choose datetime..</Text>
          
        <View style={{ display: this.state.isDatePickerVisible ? "flex" : "none" }}>
         <DatePickerIOS locale="no" mode={"datetime"} minuteInterval={10} minimumDate={new Date(2018,11,1)} maximumDate={new Date(2018,11,31)}
          style={{ borderWidth: 1, borderColor: 'gray'}} onDateChange={this.setDate} date={this.state.chosenDate} />
         <Text>{this.state.chosenDate.toLocaleDateString() + " " + this.state.chosenDate.toLocaleTimeString()}</Text>
        </View>
  
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
  },
});





React Native apps with Expo - Radio buttons

This article will present how you can add Radio buttons into your React Native app. I am using Expo to build the React Native app and Visual Studio code as the editor and IDE. You can run this app by downloading the Expo app from App Store and run the following sample by opening the Camera on your mobile and scan the QR code. This will launch Expo app and run the app. WidgetDemos - React Native app with radio buttons demo First off, you can install Expo using npm and the following command: npm install expo-cli --global You can initialize a project using these two commands: expo init MyRadioButtonProject cd MyRadioButtonProject expo start Expo's website contains good documentation here: We can use the built in React Native control SegmentedControlIOS from React native and the following code:

import React, { Component } from 'react';
import { StyleSheet, Text, View, SegmentedControlIOS } from 'react-native';

export default class Widget extends Component {

    constructor(props) {
        super(props);

    this.state = {
            selectedMoreFruitIndex: 0,
        };

    }

    setMoreFruitIndex(event) {
        this.setState({ selectedMoreFruitIndex: event.nativeEvent.selectedSegmentIndex })
    };

   render(){

   return (
            <View>

                <SegmentedControlIOS
                    values={['banana', 'apple']}
                    selectedIndex={this.state.selectedMoreFruitIndex}
                    onChange={(event) => this.setMoreFruitIndex(event)}
                />

                
            </View >
        );

  };

..


The SegmentControlIOS is simple to get started with, but it is not so easy to set up with compound objects, it only supports a string array. Hence, you cannot specify a label and a value to each radio button item. Instead use the SegmentedControls from the 'react-native-radio-buttons' package. We import this npm package using: npm install react-native-radio-buttons --save The npm package has got a home and documentation here: https://www.npmjs.com/package/react-native-radio-buttons First off, we define an array of fruit objects and put these into a state variable (array of objects) inside the constructor

 constructor(props) { 
..
 let moreFruits = [{ label: 'Strawberry', value: 'Strawberry' },
        { label: 'Melon', value: 'Melon' },
        { label: 'Pineapple', value: 'Pineapple' },
        { label: 'Grapes', value: 'Grapes' },
        ];

        this.state = {
            selectedFruit: 'Pick your favorite fruit',
            selectedMoreFruitIndex: 0,
            selectedMoreFruit: null,
            moreFruits: moreFruits
        } 

.. }
We use the SegmentedControls by importing at the top and defining it inside the View returned in the render method:

import RadioButtons, { SegmentedControls } from 'react-native-radio-buttons'; //RadioButtons not needed in this example

//..inside views of render

  <SegmentedControls
                    options={this.state.moreFruits}
                    direction='row'
                    onSelection={option => this.setSelectedOption(option)}
                    selectedOption={this.state.selectedMoreFruit}
                    extractText={(option) => option.label}
                ></SegmentedControls>

The method setSelectedOption and more can be read in the code listing below of Widget.js. I include also App.js below, the starting component: Widget.js
import React, { Component } from 'react';
import { StyleSheet, Text, View, TextInput, Button, RadioGroup, TouchableWithoutFeedback, SegmentedControlIOS } from 'react-native';
import { Dropdown } from 'react-native-material-dropdown';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import RadioButtons, { SegmentedControls } from 'react-native-radio-buttons';

export default class Widget extends Component {

    constructor(props) {
        super(props);


        let moreFruits = [{ label: 'Strawberry', value: 'Strawberry' },
        { label: 'Melon', value: 'Melon' },
        { label: 'Pineapple', value: 'Pineapple' },
        { label: 'Grapes', value: 'Grapes' },
        ];

        this.state = {
            selectedFruit: 'Pick your favorite fruit',
            reasonFruit: '',
            isFruitButtonClicked: 'no',
            selectedMoreFruitIndex: 0,
            moreFruits: moreFruits
        }
    }

    setSelectedOption(selectedOption) {
        var selectedMoreFruit = this.state.moreFruits.find(item => item.label == selectedOption.label);
        this.setState({ selectedMoreFruit: selectedMoreFruit })
    }

    renderOption(option, selected, onSelect, index) {
        const style = selected ? { backgroundColor: 'blue' } : { backgroundColor: 'red' };

        return (
            <TouchableWithoutFeedback onPress={onSelect} key={index}>
                <Text style={style}>{option.label}</Text>
            </TouchableWithoutFeedback>
        );
    }

    renderContainer(optionNodes) {
        return <View>optionNodes</View>;
    }

    setMoreFruitIndex(event) {
        this.setState({ selectedMoreFruitIndex: event.nativeEvent.selectedSegmentIndex })
    }


    render() {
        let data = [
            { label: 'Banana', value: 'Banana' },
            { label: 'Apple', value: 'Apple' },
            { label: 'Kiwi', value: 'Kiwi' }

        ];

        return (
            <View>

                <SegmentedControlIOS
                    values={['one', 'two']}
                    selectedIndex={this.state.selectedMoreFruitIndex}
                    onChange={(event) => this.setMoreFruitIndex(event)}
                />

                <Text>Selected option of react-native-radio-buttons: {this.state.selectedMoreFruit ? this.state.selectedMoreFruit.value : 'no fruit'}</Text>

                <SegmentedControls
                    options={this.state.moreFruits}
                    direction='row'
                    onSelection={option => this.setSelectedOption(option)}
                    selectedOption={this.state.selectedMoreFruit}
                    extractText={(option) => option.label}
                ></SegmentedControls>

                <Text style={{ fontWeight: 'bold' }}>Select favorite fruit then</Text>
                <Dropdown onChangeText={(val) => this.setState({ selectedFruit: val })} style={{ backgroundColor: 'aliceblue' }} data={data} />
                <Text>You selected: {this.state.selectedFruit}</Text>
                <Text></Text>


                <Text>Reason for fruit choice: {this.state.reasonFruit}</Text>
                <TextInput onChangeText={(val) => this.setState({ reasonFruit: val })} style={{ borderColor: 'black', borderRadius: 4, borderWidth: 1, backgroundColor: 'aliceblue' }} value={this.state.reasonFruit}></TextInput>

                <Text></Text>




                {/* <Button color="#841584" accessibilityLabel="Click this button!" onPress={(val) => this.setState({ isFruitButtonClicked: 'yes' })} title="Fruit button" />
                <Text>Is Fruit button clicked: {this.state.isFruitButtonClicked}</Text> */}

            </View >
        );
    }

    sayHello(val) {
        this.setState({ selectedFruit: 'You selected: ' + val })
    }


}

App.js

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Widget from './Widget'
import { Col, Row, Grid } from 'react-native-easy-grid';

export default class App extends React.Component {
  render() {
    return (
      // Try setting `alignItems` to 'flex-start'
      // Try setting `justifyContent` to `flex-end`.
      // Try setting `flexDirection` to `row`.
      <View style={{
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'stretch',
      }}>
        <View style={{ width: '80%', height: 50 }}>
          <Text style={{ fontSize: 18, flexWrap: 'wrap' }} >Welcome to the React Native apps demo!</Text>
        </View>
        <View style={{ height: 150 }}>
          <Widget />
        </View>
        <View style={{ height: 100 }} />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    flexDirection: 'column',
    backgroundColor: '#fff',
    alignItems: 'stretch'
  },
});


Lastly, remember to set the selected object in the correct manner, using the find method for example on the state variable. This to ensure we are pointing at the right selected object in the SegmentedControls control.

   setSelectedOption(selectedOption) {
        var selectedMoreFruit = this.state.moreFruits.find(item => item.label == selectedOption.label);
        this.setState({ selectedMoreFruit: selectedMoreFruit })
    }