Flutter: как использовать Firebase Storage для получения новых данных при обновлении файла JSON в Firebase Storage - PullRequest
0 голосов
/ 04 августа 2020

В настоящее время я отображаю данные, вызывая файл JSON из Firebase Storage, но я хочу, чтобы вместо загрузки JSON файла каждый раз отображались данные => Я проверю, есть ли файл JSON из Firebase Store изменился:

  • Если он изменился => загрузить новый файл JSON в локальный каталог и отобразить его.
  • В противном случае => отобразить старый файл JSON в Локальный каталог (этот старый JSON файл будет загружен при первом открытии приложения)

О JSON Файл

Это JSON ссылка после загрузки JSON в хранилище Firebase:

https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json?alt=media&token=92e3d416-62dc-4137-93a3-59ade95ac38f

Насколько мне известно, эта ссылка состоит из двух частей:

Первая часть : https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json

Последняя часть : ?alt=media&token= + 2e3d416-62dc-4137-93a3-59ade95ac38f (это значение String: "downloadTokens" в Первая часть )

В Первой части ссылки есть вся информация о файле JSON, и особенно я думаю, что значение String "обновлено" может использоваться в качестве условия для загрузки файлов или нет.

Пример. "updated": "2020-08-04T14:30:10.920Z",

Значение этой Строка обновлена ​​ будет меняться каждый раз, когда я загружаю новый JSON файл с тем же именем, что и старый JSON файл, но загрузка по ссылке не будет изменение.

Шаги

Итак, я хочу сделать следующее:

  1. Создать файл для хранения Строка «обновлена» в локальном каталоге (Пример. "updated": null) и где хранить JSON файл после загрузки в Локальный каталог
  2. Открыть приложение
  3. Проверить Строка "обновлена" в ссылке Первая часть :
  • Случай A : если значение String " обновлено "в Первая часть != значение строки" обновлено "в Локальном каталоге =>

    • Шаг 1: скачать JSON файл ( ссылка: First part + ?alt=media&token= + downloadTokens) в Локальный каталог (Если старый файл json уже существует, он будет заменен)
    • Шаг 2: перезаписать значение строки «обновлено» в Локальном каталоге по значению строки «обновлено» в хранилище Firebase
    • Шаг 3: доступ к JSON файлу в локальном каталоге для отображения данных
  • Вариант B : if val ue строки «обновлено» в Первая часть == значение строки «обновлено» в Локальный каталог => ничего не делать, просто откройте JSON файл в Локальном каталоге для отображения данных

Я знаю, что это много вопросов для одного сообщения, я новичок ie с кодом, и если я разделю его на несколько сообщений, то мне будет очень сложно их объединить. Так что я надеюсь, что ответ с полным кодом будет отличным. Спасибо. Это основной файл:

import 'package:ask/model/load_data_model.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

class LoadDataPage extends StatefulWidget {
  @override
  _LoadDataPageState createState() => _LoadDataPageState();
}

class DataServices {
  static const String url = 'https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json?alt=media&token=92e3d416-62dc-4137-93a3-59ade95ac38f';

  static Future<List<Data>> getData() async {
    try {
      final response = await http.get(url);
      if (200 == response.statusCode) {
        final List<Data> data = dataFromJson(response.body);
        return data;
      } else {
        return List<Data>();
      }
    } catch (e) {
      return List<Data>();
    }
  }
}

class _LoadDataPageState extends State<LoadDataPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('Load Data')),
        body: FutureBuilder(
            future: DataServices.getData(),
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              List<Widget> children;
              List<Data> _data = snapshot.data;
              if (snapshot.hasData) {
                return ListView.builder(
                  itemCount: _data.length,
                  itemBuilder: (context, index) {
                    return Column(
                      children: [Text(_data[index].data)],
                    );
                  },
                );
              } else {
                children = <Widget>[SizedBox(child: CircularProgressIndicator(), width: 60, height: 60), const Padding(padding: EdgeInsets.only(top: 16), child: Text('Loading...'))];
              }
              return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: children));
            }));
  }
}

Другие шаги

Ответ EdwynZN отлично сработал для меня, однако я редактирую сообщение, чтобы добавить еще один случай, который, как я думаю, сделает загрузку страницы как можно скорее, поэтому, пожалуйста, снова помогите мне: После открытия страницы => readFile> compareLastUpdate> _lastUpdateDB & _createFile

  • Случай A : при первом открытии приложения => readFile: false> _lastUpdateDB & _createFile> readFile снова
  • Случай B : приложение открывается не в первый раз:
    • данные по-прежнему загружаются сразу из старый JSON, в то же время, работать в фоновом режиме: compareLastUpdate:
      • Если время обновления одинаковое => ничего не делать
      • Если время обновления отличается => _lastUpdateDB & _createFile

P / S: В этом потоке, когда они открывают страницу второй раз, будут отображаться новые данные, верно? Но мне интересно, если использовать StatefulWidget => после того, как новый JSON файл будет перезаписан на старый JSON файл =>, будут ли после этого на экране телефона отображаться новые данные?

1 Ответ

1 голос
/ 06 августа 2020

Я бы порекомендовал использовать shared_preferences , чтобы сохранить дату последнего обновления в виде строки

import 'package:shared_preferences/shared_preferences.dart';
import 'package:path_provider/path_provider.dart';

/// Move them outside of the class as Top Level functions
List<Data> readFile(File file) {
  try{
    String data = file.readAsStringSync();
    return dataFromJson(data);
  } catch(e){
    print(e.toString());
    return List<Data>(); // or return an empty list, up to you
  }
}

// No need of encoder now because response body is already a String
void writeFile(Map<String, dynamic> arg) =>
  arg['file']?.writeAsStringSync(arg['data'], flush: true);

class DataServices {

  DateTime dateApi;

  static const String url = 'https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json?alt=media&token=92e3d416-62dc-4137-93a3-59ade95ac38f';
  static const String urlUpdate = 'https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json';

  Future<List<Data>> getData() async {
    bool update = await compareLastUpdate;
    if(update) { // that means the update times are the same, so retrieving form json file is better than doing http request
       final file  = await _createFile();
       if(await file.exists()) return await compute(readFile, file);
       else return null; //or an empty List
       // If it doesn't exists (probably first time running the app)
       // then retrieve an empty list, null or check how to fill the list from somewhere else
    }
    try {
      final response = await http.get(url);
      final SharedPreferences preferences = await SharedPreferences.getInstance();
      if (200 == response.statusCode) {
        final List<Data> data = await compute(dataFromJson, response.body);
        final file  = await _createFile();
        Map<String, dynamic> args = {
          'file': file,
          'data': response.body // pass the return body instead of the data
        };
        await compute(writeFile, args);
        await preferences.setString('updateDate', dateApi.toString()); //Save the new date
        return data;
      } else {
        return List<Data>();
      }
    } catch (e) {
      return List<Data>();
    }
  }

 File _createFile() async{
   Directory tempDir = await getTemporaryDirectory(); // or check for a cache dir also
   return File('${tempDir.path}/Data.json');
 }


Future<bool> get compareLastUpdate async{
  final dateCache = await _lastUpdateDB;
  dateApi = await _lastUpdateApi;

  if(dateCache == null) return false;    
  return dateApi?.isAtSameMomentAs(dateCache) ?? false; // or just isAfter()
  // If dateApi is null (an error conection or some throw) just return false or throw an error and 
  // catch it somewhere else (and give info to the user why it couldn't update)
}

Future<DateTime> get _lastUpdateApi async{
  try {
     final response = await http.get(urlUpdate);
     DateTime dateTime;
     if (200 == response.statusCode) {
       final data = jsonDecode(response.body));
       dateTime = DateTime.tryParse(data['updated'] ?? '');
     } 
     return dateTime;
   } catch (e) {
     return null;
   }
}

  Future<DateTime> get _lastUpdateDB async{
    final SharedPreferences preferences = await SharedPreferences.getInstance();
    return DateTime.tryParse(preferences.getString('updateDate') ?? ''); // Or if it's null use an old date
    // The first time the app opens there is no updateDate value, so it returns null, if that
    // happens replace it by an old date, one you know your api will be always newer,
    // Ex: 1999-08-06 02:07:53.973 Your Api/App didn't even exist back then
    // Or just use an empty String so the tryParser returns null
  }
}

Затем в виджете вы просто назовете это так же

class _LoadDataPageState extends State<LoadDataPage> {
  final DataServices services = DataServices();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text('Load Data')),
        body: FutureBuilder(
            future: services.getData(),
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              List<Widget> children;
              List<Data> _data = snapshot.data;
              if (snapshot.hasData) {
                return ListView.builder(
                  itemCount: _data.length,
                  itemBuilder: (context, index) {
                    return Column(
                      children: [Text(_data[index].data)],
                    );
                  },
                );
              } else {
                children = <Widget>[SizedBox(child: CircularProgressIndicator(), width: 60, height: 60), const Padding(padding: EdgeInsets.only(top: 16), child: Text('Loading...'))];
              }
              return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: children));
            }));
  }
}

Также вы можете проверить пакет Dio , у которого есть некоторые функции через http, которые позволяют добавлять параметры к URL-адресу

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...