3

currently, my apps have many assets (images, sound, font, json, SQL-lite database file, etc). All have defined in pubspec.yaml

However, due to a request to reduce APK size, I need some of them to be downloaded when Apps be launched and save it to storage, so no need to download it next time.

if assets are not yet ready, it should waiting a sec and show loading bar circle.

The question is how to do this thing, Any Example?

questionasker
  • 2,536
  • 12
  • 55
  • 119

1 Answers1

2

The easiest way to do it is to download your files as zip (archived file) and unpack them in the path of the application storage directory getApplicationDocumentsDirectory

You will use this list of packages: archive , http and path_provider

The pubspec.yaml will look like

version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  path_provider: ^1.1.0
  http: ^0.12.0+2
  archive: ^2.0.8

dev_dependencies:
  flutter_test:
    sdk: flutter
  uses-material-design: true
  assets:
    - assets/images/

The main.dart file which coronations your app will look like Note that api is the URL of your file without the file name.

main.dart

import 'dart:io';

import 'package:archive/archive.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';

import 'data.dart';

const api =
    'https://firebasestorage.googleapis.com/v0/b/playground-a753d.appspot.com/o';

enum AppTheme { candy, cocktail }

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(AppTheme.candy),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final AppTheme theme;

  MyHomePage(this.theme);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  AppTheme _theme;
  String _dir;
  List<String> _images;

  @override
  void initState() {
    super.initState();
    _theme = widget.theme;
    _images = data[_theme];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.style),
        onPressed: () async {
          if (_theme == AppTheme.candy) {
            await _downloadAssets('cocktail');
          }
          setState(() {
            _theme =
                _theme == AppTheme.candy ? AppTheme.cocktail : AppTheme.candy;
            _images = data[_theme];
          });
        },
      ),
      body: ListView.builder(
          itemCount: _images.length,
          itemBuilder: (BuildContext context, int index) {
            return _getImage(_images[index], _dir);
          }),
    );
  }

  Widget _getImage(String name, String dir) {
    if (_theme != AppTheme.candy) {
      var file = _getLocalImageFile(name, dir);
      return Image.file(file);
    }
    return Image.asset('assets/images/$name');
  }

  File _getLocalImageFile(String name, String dir) => File('$dir/$name');

  Future<void> _downloadAssets(String name) async {
    if (_dir == null) {
      _dir = (await getApplicationDocumentsDirectory()).path;
    }

    if (!await _hasToDownloadAssets(name, _dir)) {
      return;
    }
    var zippedFile = await _downloadFile(
        '$api/$name.zip?alt=media&token=7442d067-a656-492f-9791-63e8fc082379',
        '$name.zip',
        _dir);

    var bytes = zippedFile.readAsBytesSync();
    var archive = ZipDecoder().decodeBytes(bytes);

    for (var file in archive) {
      var filename = '$_dir/${file.name}';
      if (file.isFile) {
        var outFile = File(filename);
        outFile = await outFile.create(recursive: true);
        await outFile.writeAsBytes(file.content);
      }
    }
  }

  Future<bool> _hasToDownloadAssets(String name, String dir) async {
    var file = File('$dir/$name.zip');
    return !(await file.exists());
  }

  Future<File> _downloadFile(String url, String filename, String dir) async {
    var req = await http.Client().get(Uri.parse(url));
    var file = File('$dir/$filename');
    return file.writeAsBytes(req.bodyBytes);
  }
}

Then you have to list all files and added them (logically to the corresponding theme)

data.dart:

import 'main.dart' show AppTheme;

const Map<AppTheme, List<String>> data = const {
  AppTheme.candy: [
    'art-background-blue-1289363.jpg',
    'assortment-bright-candy-1043519.jpg',
    'bright-candies-cherry-1405760.jpg',
    'bright-candies-colorful-539447.jpg',
    'bright-candy-chewy-1328885.jpg',
  ],
  AppTheme.cocktail: [
    'alcohol-alcoholic-beverage-beverage-1304540.jpg',
    'alcohol-alcoholic-beverage-beverage-1723638.jpg',
    'alcohol-black-background-close-up-800390.jpg',
    'alcoholic-beverage-beverage-cocktail-970197.jpg',
    'bar-beverage-blur-338713.jpg',
  ]
};

For more information check this Github project and Medium article

Shady Mohamed Sherif
  • 15,003
  • 4
  • 45
  • 54
  • Welcome, if It worked for you please accept the answer :) – Shady Mohamed Sherif Nov 01 '19 at 05:32
  • Hi, I still confused on this code: `for (var file in archive) { var filename = '$_dir/${file.name}'; if (file.isFile) { var outFile = File(filename); outFile = await outFile.create(recursive: true); await outFile.writeAsBytes(file.content); } }` why we need it. how about if the zip file contain root folder? – questionasker Nov 12 '19 at 08:53
  • Welcome again, This code supports a zip file that contains a list of files directly. If you are going to and folders It will be more complicated. you can check the example in the archive package https://pub.dev/packages/archive#-example-tab- . If you did'y figure it out, It will try to write the code for you. – Shady Mohamed Sherif Nov 12 '19 at 10:14
  • I think i will use zip file contain no root folder. However, to verify my apps has to download the zip and extract it, i need to know the location of the assets file. I will use file manager apps to browse it on Android. Any idea? – questionasker Nov 12 '19 at 11:34
  • You can see applications files by normal file explorer. If you want to check the files by yourself not a code, first you have to print `_dir` var which contains `(await getApplicationDocumentsDirectory()).path`. then reach to that path using android studio `device file explorer` you will find on the right-down corner. Note that your app must be in debug mode. – Shady Mohamed Sherif Nov 12 '19 at 12:09