Flutter app performs downloads and tasks on startup without user interaction

26 Views Asked by At
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:archive/archive.dart';
import 'package:path_provider/path_provider.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ZIP List',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyPage(),
    );
  }
}

class Item {
  final int id;
  String titulo; // title
  String descricao; // description
  String img;
  double nota; // rating

  Item({
    required this.id,
    required this.titulo, // title
    required this.descricao, // description
    required this.img,
    required this.nota, // rating
  });

  void updateData(Map<String, dynamic> newData) {
    titulo = newData['titulo'] ?? titulo; // title
    descricao = newData['descricao'] ?? descricao; // description
    img = newData['img'] ?? img;
    nota = newData['nota']?.toDouble() ?? 0.0; // rating
  }
}

class MyPage extends StatefulWidget {
  const MyPage({Key? key}) : super(key: key);

  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  List<Item> itemList = [];

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

  Future<void> _loadList() async {
    final response = await http.get(
      Uri.parse(
          'https://raw.githubusercontent.com/SGomes764/database/main/lista/database.json'),
    );

    if (response.statusCode == 200) {
      Map<String, dynamic>? data = json.decode(response.body);

      if (data != null && data.containsKey('items')) {
        List<dynamic> jsonList = data['items'];

        List<Item> tempItemList = [];

        for (var jsonItem in jsonList) {
          Item item = Item(
            id: jsonItem['id'],
            titulo: jsonItem['titulo'] ?? 'Click to download', // title
            descricao: jsonItem['descricao'] ?? 'No information was found', //description
            img: jsonItem['img'],
            nota: jsonItem['nota']?.toDouble() ?? 0.0, // rating
          );

          // Check if the data has already been transferred
          if (await _transferredData(item)) {
            print('\t\t«----{Using transferred data for ID: ${item.id}}----»\n');
          } else {
            print('\t\t«----{Using default data for ID: ${item.id}}----»\n');
            await _downloadZip(item);
          }

          tempItemList.add(item);
        }

        print(
            '\t\t«----{Loaded ${tempItemList.length} items from the server.}----»\n');

        setState(() {
          itemList = tempItemList;
        });
      } else {
        print('\t\t«----{Invalid or missing data from the server.}----»\n');
        throw Exception('Invalid or missing data');
      }
    } else {
      print(
          '\t\t«----{Failed to load data from the server. Status code: ${response.statusCode}}----»\n');
      throw Exception('Failed to load data');
    }
  }

  Future<bool> _transferredData(Item item) async {
    // Check if the data for the item has already been transferred
    final appDocumentsDir = await getApplicationDocumentsDirectory();
    final extractedDir = Directory("${appDocumentsDir.path}/extracted");
    final jsonFile = File("${extractedDir.path}/${item.id}.json");

    return jsonFile.existsSync();
  }

  Future<void> _downloadZip(Item item) async {
    // Exclude IDs 1 to 3 from the download process
    if (item.id >= 1 && item.id <= 3) {
      print('\t\t«----{Skipping download for ID: ${item.id}}----»\n');
      return;
    }

    const baseUrl =
        'https://raw.githubusercontent.com/SGomes764/database/main/lista/';
    final url = '$baseUrl${item.id}.zip';

    print('\t\t«----{ZIP URL: $url}----»\n');

    try {
      var response = await http.get(Uri.parse(url));

      if (response.statusCode == 200) {
        print('\t\t«----{Downloading file for ID card ${item.id}...}----»\n');

        // Get the app's documents directory
        final appDocumentsDir = await getApplicationDocumentsDirectory();

        // Create the target directory for the file downloads
        final downloadDir = Directory("${appDocumentsDir.path}/downloads");
        await downloadDir.create(recursive: true);

        // Save the ZIP file to the downloads directory
        var zipFile = File("${downloadDir.path}/${item.id}_file.zip");
        await zipFile.writeAsBytes(response.bodyBytes);

        print('\t\t«----{File downloaded successfully.}----»\n');

        // Extract the ZIP file
        await _extractZip(zipFile, item);

        // Display a success message after download and extraction
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(
                'Download and extraction completed for ID: ${item.id}'),
          ),
        );

        print("\t\t«----{File downloaded and extracted successfully.}----»\n");
      } else {
        print(
            "\t\t«----{Failed to download file. Status code: ${response.statusCode}}----»\n");
      }
    } catch (error) {
      print("\t\t«----{Error during file download: $error}----»\n");
    }
  }

  Future<void> _extractZip(File zipFile, Item item) async {
    try {
      // Get the temporary directory for extraction
      Directory tempDir = await getTemporaryDirectory();

      // Create the target directory for extraction
      Directory extractDir = Directory("${tempDir.path}/extracted");
      await extractDir.create();

      // Read the contents of the ZIP file
      List<int> bytes = await zipFile.readAsBytes();
      Archive archive = ZipDecoder().decodeBytes(bytes);

      print('\t\t«----{Starting ZIP extraction...}----»\n');

      // Extract the content to the target directory
      for (ArchiveFile file in archive) {
        String fileName = file.name;
        String filePath = "${extractDir.path}/$fileName";

        if (file.isFile) {
          print('\t\t«----{Extracting file: $fileName}----»\n');
          File(filePath)
            ..createSync(recursive: true)
            ..writeAsBytesSync(file.content);

          // Check if the extracted file is a JSON file
          if (fileName.endsWith('.json')) {
            // Read the content of the JSON file
            String jsonContent = await File(filePath).readAsString();
            print('File path: $filePath');
            Map<String, dynamic> jsonData = json.decode(jsonContent);

            // Associate the downloaded JSON data with the corresponding Item
            setState(() {
              item.updateData(jsonData);
            });

            // Save the JSON to a file to indicate that the data has been transferred
            await File("${extractDir.path}/${item.id}.json")
                .writeAsString(jsonContent);
          }
        }
      }

      print('\t\t«----{ZIP extraction completed successfully}----»\n');
    } catch (error) {
      print("\t\t«----{Error during ZIP extraction: $error}----»\n");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: const Text('Listing'),
      ),
      body: ListView.builder(
        itemCount: itemList.length,
        itemBuilder: (context, index) {
          return _buildItemCard(itemList[index]);
        },
      ),
    );
  }

  Widget _buildItemCard(Item item) {
    return GestureDetector(
      onTap: () {
        print('\t\t«----{Pressed card ID: ${item.id}}---»\n');
        _downloadZip(item);
      },
      child: Card(
        margin: const EdgeInsets.all(8.0),
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Expanded(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '${item.titulo} | ${item.id}', // title | id
                      style: const TextStyle(
                          fontSize: 18.0, fontWeight: FontWeight.bold),
                    ),
                    Text(
                      item.descricao, // description
                      style: const TextStyle(
                        fontSize: 16.0,
                      ),
                    ),
                    const SizedBox(
                      height: 8.0,
                    ),
                    Text(
                      'Rating: ${item.nota}',
                      style:
                      const TextStyle(fontSize: 14.0, color: Colors.grey),
                    ),
                  ],
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Image.network(
                item.img,
                width: 75.0,
                height: 75.0,
                fit: BoxFit.cover,
                errorBuilder: (BuildContext context, Object error,
                    StackTrace? stackTrace) {
                  return Image.network(
                    'https://cdn-icons-png.flaticon.com/512/5064/5064064.png',
                    width: 75.0,
                    height: 75.0,
                    fit: BoxFit.cover,
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Currently the problem lies in the need to show the data that is in JSON if it has already been transferred, when the app is started the data is presented but there are attempts to download it from the code, which is not intended. Downloads are only intended when the onTap event occurs.

          // Check if the data has already been transferred
          if (await _transferredData(item)) {
            print('\t\t«----{Using transferred data for ID: ${item.id}}----»\n');
          } else {
            print('\t\t«----{Using default data for ID: ${item.id}}----»\n');
            await _downloadZip(item);
          }

I already tried to remove the _downloadZip(item) call from this logic but the code did not load the data that was already transferred

 Future<bool> _dadosTransferidos(Item item) async {
    // Checks whether the data for the item has already been transferred
    final appDocumentsDir = await getApplicationDocumentsDirectory();
    final extractedDir = Directory("${appDocumentsDir.path}/extracted");
    final jsonFile = File("${extractedDir.path}/${item.id}.json");

    if (jsonFile.existsSync()) {
      // If file exist, load data
      String jsonContent = await jsonFile.readAsString();
      Map<String, dynamic> jsonData = json.decode(jsonContent);
      item.atualizarDados(jsonData);
      return true;
    }

    return false;
  }

I also tried to implement functions and logic similar to this and it didn't result in anything, maybe they work but they were poorly implemented by me

0

There are 0 best solutions below