Implementing In-App Updates to Allow Users to Update Your App Without Leaving It in Flutter

In-app updates provide a seamless way for users to update your Flutter application without leaving the app. This feature improves user experience by reducing friction and encouraging users to stay up-to-date with the latest features and bug fixes. The Flutter in_app_update package simplifies the implementation of in-app updates, offering both flexible and immediate update flows.

What are In-App Updates?

In-app updates allow users to update your app directly from within the app, without redirecting them to the Google Play Store (for Android apps). This feature supports two types of updates:

  • Flexible Updates: A non-blocking update that users can accept or postpone. Suitable for feature enhancements and minor updates.
  • Immediate Updates: A blocking update that requires users to update immediately to continue using the app. Typically used for critical bug fixes and security updates.

Why Implement In-App Updates?

  • Improved User Experience: Streamlines the update process, reducing friction for users.
  • Higher Adoption Rates: Encourages users to update more frequently, ensuring they have the latest version.
  • Timely Updates: Facilitates quicker deployment of critical bug fixes and security updates.

How to Implement In-App Updates in Flutter

To implement in-app updates in Flutter, follow these steps:

Step 1: Add the in_app_update Package

Add the in_app_update package to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  in_app_update: ^3.1.0

Then, run flutter pub get to install the package.

Step 2: Import the Package

In your Dart file, import the in_app_update package:

import 'package:in_app_update/in_app_update.dart';

Step 3: Check for Update Availability

Use the InAppUpdate.checkForUpdate() method to check if an update is available:

import 'package:flutter/material.dart';
import 'package:in_app_update/in_app_update.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'In-App Update Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  AppUpdateInfo? _updateInfo;

  @override
  void initState() {
    super.initState();
    checkForUpdate();
  }

  Future checkForUpdate() async {
    InAppUpdate.checkForUpdate().then((info) {
      setState(() {
        _updateInfo = info;
      });
    }).catchError((e) {
      print(e);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('In-App Update Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Check for Update:',
            ),
            ElevatedButton(
              onPressed: () {
                checkForUpdate();
              },
              child: Text('Check'),
            ),
            if (_updateInfo != null)
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Text(
                  'Update Availability: ${_updateInfo?.updateAvailability} n'
                  'Install Status: ${_updateInfo?.installStatus} n'
                  'Update Priority: ${_updateInfo?.updatePriority} n'
                  'Client Version Stale Days: ${_updateInfo?.clientVersionStalenessDays}',
                ),
              ),
          ],
        ),
      ),
    );
  }
}

Step 4: Implement Flexible Updates

To implement a flexible update, request the update using InAppUpdate.startFlexibleUpdate(). Also you can request immediate update as follows

Future<void> startFlexibleUpdate() async {
  try {
    final result = await InAppUpdate.startFlexibleUpdate();
    if (result == AppUpdateResult.success) {
      showSnack('AppUpdateResult.success');
      // Perform any additional actions after the update has started successfully
    } else {
      showSnack('AppUpdateResult failed: $result');
    }
  } catch (e) {
    showSnack(e.toString());
  }
}

Complete the update by calling InAppUpdate.completeFlexibleUpdate() after the user accepts the update:

Future<void> completeFlexibleUpdate() async {
  try {
    await InAppUpdate.completeFlexibleUpdate();
    showSnack("Success!");
  } catch (e) {
    showSnack(e.toString());
  }
}

Step 5: Implement Immediate Updates

To implement an immediate update, call InAppUpdate.performImmediateUpdate():

Future<void> performImmediateUpdate() async {
  try {
    final result = await InAppUpdate.performImmediateUpdate();
    if (result == AppUpdateResult.success) {
      showSnack('AppUpdateResult.success');
      // Perform any additional actions after the update has started successfully
    } else {
      showSnack('AppUpdateResult failed: $result');
    }
  } catch (e) {
    showSnack(e.toString());
  }
}

In the MyHomePage class, add these buttons:


ElevatedButton(
  onPressed: () {
    startFlexibleUpdate();
  },
  child: Text('Start Flexible Update'),
),
ElevatedButton(
  onPressed: () {
    completeFlexibleUpdate();
  },
  child: Text('Complete Flexible Update'),
),
ElevatedButton(
  onPressed: () {
    performImmediateUpdate();
  },
  child: Text('Start Immediate Update'),
),

Final Result:


import 'package:flutter/material.dart';
import 'package:in_app_update/in_app_update.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'In-App Update Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  AppUpdateInfo? _updateInfo;

  @override
  void initState() {
    super.initState();
    checkForUpdate();
  }

  Future checkForUpdate() async {
    InAppUpdate.checkForUpdate().then((info) {
      setState(() {
        _updateInfo = info;
      });
    }).catchError((e) {
      showSnack(e.toString());
    });
  }

  Future<void> startFlexibleUpdate() async {
    try {
      final result = await InAppUpdate.startFlexibleUpdate();
      if (result == AppUpdateResult.success) {
        showSnack('AppUpdateResult.success');
        // Perform any additional actions after the update has started successfully
      } else {
        showSnack('AppUpdateResult failed: $result');
      }
    } catch (e) {
      showSnack(e.toString());
    }
  }

  Future<void> completeFlexibleUpdate() async {
    try {
      await InAppUpdate.completeFlexibleUpdate();
      showSnack("Success!");
    } catch (e) {
      showSnack(e.toString());
    }
  }

  Future<void> performImmediateUpdate() async {
    try {
      final result = await InAppUpdate.performImmediateUpdate();
      if (result == AppUpdateResult.success) {
        showSnack('AppUpdateResult.success');
        // Perform any additional actions after the update has started successfully
      } else {
        showSnack('AppUpdateResult failed: $result');
      }
    } catch (e) {
      showSnack(e.toString());
    }
  }

  void showSnack(String text) {
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(text)),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('In-App Update Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Check for Update:',
            ),
            ElevatedButton(
              onPressed: () {
                checkForUpdate();
              },
              child: Text('Check'),
            ),
            if (_updateInfo != null)
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Text(
                  'Update Availability: ${_updateInfo?.updateAvailability} n'
                  'Install Status: ${_updateInfo?.installStatus} n'
                  'Update Priority: ${_updateInfo?.updatePriority} n'
                  'Client Version Stale Days: ${_updateInfo?.clientVersionStalenessDays}',
                ),
              ),
            ElevatedButton(
              onPressed: () {
                startFlexibleUpdate();
              },
              child: Text('Start Flexible Update'),
            ),
            ElevatedButton(
              onPressed: () {
                completeFlexibleUpdate();
              },
              child: Text('Complete Flexible Update'),
            ),
            ElevatedButton(
              onPressed: () {
                performImmediateUpdate();
              },
              child: Text('Start Immediate Update'),
            ),
          ],
        ),
      ),
    );
  }
}

Step 6: Handle Update Availability

Check the updateAvailability property of the AppUpdateInfo object to determine if an update is available. Depending on the availability, you can trigger the appropriate update flow (flexible or immediate).

if (_updateInfo?.updateAvailability == UpdateAvailability.updateAvailable) {
    // Update is available, trigger either flexible or immediate update
}

Testing In-App Updates

To test in-app updates, you need to use a signed APK uploaded to the Google Play Store. Follow these steps:

  1. Upload APK to Play Store: Upload your APK to the Google Play Console and publish it to an internal test track or a closed test track.
  2. Install App from Play Store: Install the app on your test device from the Play Store.
  3. Test Updates: Release a newer version of the app to the same test track, and then test the in-app update functionality on your device.

Conclusion

Implementing in-app updates in Flutter provides a smoother and more efficient update experience for your users. By using the in_app_update package, you can easily integrate both flexible and immediate updates, ensuring that users stay up-to-date with the latest features and bug fixes. This improves user satisfaction and helps maintain a consistent experience across your user base.