OTA Updates download event not fired

Hi guys,
Im using the SDK 26 and sometimes the Updates module not fire DOWNLOAD_FINISHED event when new version published. I follow the @ben post (OTA Updates Only Appear After Killing App - #2 by ben) and the docs (https://docs.expo.io/versions/v26.0.0/guides/configuring-ota-updates), any advice will be welcome.

App.json:

"updates": {
  "fallbackToCacheTimeout": 0
},

Here my code for the listener:

componentDidMount() {
  this._update_event_handler = Updates.addListener(event => {
    if (event.type === Updates.EventType.DOWNLOAD_FINISHED) {
      Alert.alert(
        I18n.t('update_title'),
        I18n.t('update_message'),
        [
          {
            text: I18n.t('accept_button'),
            onPress: () => {
              Updates.reload();
            }
          },
        ],
        { cancelable: false }
      );
    }
  });
}
componentWillUnmount() {
  this._update_event_handler && this._update_event_handler.remove();
}

Is this code correct? I have to handle something else to display alert to the user when new update found? What happens if this code runs in the background? I need to handle this using the AppState and defer the alert display?

Thanks !!!

@llamaluvr Have you been working with this API?

Hi @outatime - sorry for the delayed response here. If you’re wanting to check for updates more frequently than just when the app is started (e.g. every time it’s foregrounded), you should use the Updates.checkForUpdateAsync() method rather than just listening for events.

If there is one available, then you can fetchUpdateAsync() and reloadFromCache() grab the update and reload, as shown in the guide here.

You will need to handle AppState yourself if you want this code to be called every time the app is foregrounded.

Thanks, here I share a basic snippet for application OTA update …

I check for updates on fresh start and when the app comes to the foreground.

@esamelson do you see it well or I forget something?

app.json:

"updates": {
  "checkAutomatically": "ON_ERROR_RECOVERY"
},

App.js:

import React from 'react';
import { StyleSheet, Text, View, AppState, Alert } from 'react-native';
import { Constants, Updates } from 'expo';

export default class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      showReloadDialog: false,
    };
  }

  _checkUpdates = async () => {
    if (this._checking_update !== true) {
      console.log('Checking for an update...');
      this._checking_update = true;
      try {
        const update = await Updates.checkForUpdateAsync();
        if (update.isAvailable) {
          console.log('An update was found, downloading...');
          await Updates.fetchUpdateAsync();
        } else {
          console.log('No updates were found');
        }
      } catch (e) {
        console.log('Error while trying to check for updates', e);
      }
      delete this._checking_update;
    } else {
      console.log('Currently checking for an update');
    }
  }

  _handleAppStateChange = (nextAppState) => {
    if (nextAppState === 'active') {
      this._checkUpdates();
    }
  }

  componentDidMount() {
    AppState.addEventListener('change', this._handleAppStateChange);
    this._update_event_handler = Updates.addListener(event => {
      if (event.type === Updates.EventType.DOWNLOAD_FINISHED) {
        this.setState({
          showReloadDialog: true,
        });
      }
    });
    // fresh start check
    this._checkUpdates();
  }

  componentWillUnmount() {
    AppState.removeEventListener('change', this._handleAppStateChange);
    this._update_event_handler && this._update_event_handler.remove();
  }

  componentDidUpdate(prevProps, prevState) {
    const {
      showReloadDialog,
    } = this.state;
    if (showReloadDialog === true && showReloadDialog !== prevState.showReloadDialog) {
      Alert.alert(
        'Update',
        'New application update were found, restart must required.',
        [
          {
            text: 'Accept',
            onPress: () => {
              Updates.reloadFromCache();
            }
          },
        ],
        { cancelable: false }
      );
    }
  }

  render() {
    return (
      <View style={styles.container}>
        <Text style={{ fontWeight: 'bold' }}>Expo self update</Text>
        <Text>v.{Constants.manifest.version}</Text>
      </View>
    );
  }
}

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

Hi @outatime – from a cursory glance, it looks right to me! If you want to make it a bit simpler, you can get rid of this._update_event_handler and just put

this.setState({
  showReloadDialog: true,
});

right below await Updates.fetchUpdateAsync(), since the promise will resolve at the same time the DOWNLOAD_FINISHED event is emitted.

However, the proof is in the pudding – give it a try and see if it works :slight_smile:

Great @esamelson – yup, you are right it works as expected … thanks !!

Here the final version for the community:

import React from 'react';
import { StyleSheet, Text, View, AppState, Alert } from 'react-native';
import { Constants, Updates } from 'expo';

export default class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      showReloadDialog: false,
    };
  }

  _checkUpdates = async () => {
    if (this._checking_update !== true) {
      console.log('Checking for an update...');
      this._checking_update = true;
      try {
        const update = await Updates.checkForUpdateAsync();
        if (update.isAvailable) {
          console.log('An update was found, downloading...');
          await Updates.fetchUpdateAsync();
          this.setState({
            showReloadDialog: true,
          });
        } else {
          console.log('No updates were found');
        }
      } catch (e) {
        console.log('Error while trying to check for updates', e);
      }
      delete this._checking_update;
    } else {
      console.log('Currently checking for an update');
    }
  }

  _handleAppStateChange = (nextAppState) => {
    if (nextAppState === 'active') {
      this._checkUpdates();
    }
  }

  componentDidMount() {
    AppState.addEventListener('change', this._handleAppStateChange);
    // fresh start check
    this._checkUpdates();
  }

  componentWillUnmount() {
    AppState.removeEventListener('change', this._handleAppStateChange);
  }

  componentDidUpdate(prevProps, prevState) {
    const {
      showReloadDialog,
    } = this.state;
    if (showReloadDialog === true && showReloadDialog !== prevState.showReloadDialog) {
      Alert.alert(
        'Update',
        'New application update were found, restart must required.',
        [
          {
            text: 'Accept',
            onPress: () => {
              Updates.reloadFromCache();
            }
          },
        ],
        { cancelable: false }
      );
    }
  }

  render() {
    return (
      <View style={styles.container}>
        <Text style={{ fontWeight: 'bold' }}>Expo self update</Text>
        <Text>v.{Constants.manifest.version}</Text>
      </View>
    );
  }
}

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

Remember the app.json to prevent multiple downloads on load:

"updates": {
  "checkAutomatically": "ON_ERROR_RECOVERY"
},
9 Likes

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.