关于 Flutter 中提供者的澄清 - 如何保存数据

Clarification about provider in Flutter - How to save data

我是 Flutter 的新手,我花了一些时间了解提供商模式,我是一个 PHP 的人,所以我对它的工作原理有基本的了解,尤其是在 Laravel框架。
对我来说,提供商应该管理数据、从数据库中检索数据、操作广告保存。

据我了解,flutter 中的概念是相同的,但很难按照文档进行操作。

我创建了一个调用 REST 的简单应用程序 API,现在我想将响应添加到我的提供商以在我的所有页面和小部件中使用数据。

这是我的代码。

向我的用户显示启动画面并调用外部端点的 splash.dart 文件,此处 我想获取我的数据并添加到提供程序。

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

import 'models/maison.dart';
import 'dart:convert' as convert;
import 'package:http/http.dart' as http;

class SplashApp extends StatefulWidget {
  final VoidCallback onInitializationComplete;

  const SplashApp({
    Key key,
    @required this.onInitializationComplete,
  }) : super(key: key);

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

class _SplashAppState extends State<SplashApp> {
  @override
  void initState() {
    super.initState();
    _initializeAsyncDependencies();
  }

  Future<void> _initializeAsyncDependencies() async {
    var url = 'https://run.mocky.io/v3/95237af1-b13f-4756-b308-56c9aac93c7e';

    // Await the http get response, then decode the json-formatted response.
    var response = await http.get(url);
    if (response.statusCode == 200) {
      var jsonResponse = convert.jsonDecode(response.body);
      var data = jsonResponse['data'];
      var name = data[0]['name'];
      print('Name: $name.');
    } else {
      print('Request failed with status: ${response.statusCode}.');
    }
    // >>> initialize async dependencies <<<
    // >>> register favorite dependency manager <<<
    // >>> reap benefits <<<
    Future.delayed(
      Duration(milliseconds: 3000),
      () => widget.onInitializationComplete(),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Splash Screen',
      theme: ThemeData(
        accentColor: Colors.black,
      ),
      home: _buildBody(),
    );
  }

  Widget _buildBody() {
    return Scaffold(
      body: Stack(
        fit: StackFit.expand,
        children: <Widget>[
          Container(
            decoration: BoxDecoration(
              color: Color(0xFFDDCDC8),
            ),
          ),
          Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              Expanded(
                flex: 2,
                child: Container(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      CircleAvatar(
                        backgroundColor: Colors.white,
                        radius: 80.0,
                        child: Icon(
                          Icons.access_time,
                          color: Colors.black,
                          size: 80.0,
                        ),
                      ),
                      Padding(
                        padding: EdgeInsets.only(
                          top: 10.0,
                        ),
                      ),
                      Text(
                        'Title',
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 26.0,
                        ),
                      ),
                      SizedBox(
                        height: 16.0,
                      ),
                      Text(
                        'Subtitle',
                        style: TextStyle(
                          fontSize: 16.0,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
              Expanded(
                flex: 1,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    CircularProgressIndicator(),
                    Padding(
                      padding: EdgeInsets.only(top: 20.0),
                    ),
                    Text('Caricamento'),
                  ],
                ),
              ),
            ],
          )
        ],
      ),
    );
  }
}

这是我的 main.dart 文件,在这里我初始化了我的标签和一些东西。我在那里添加了 ChangeNotifyer 因为这里我需要数据,也许是不正确的地方?

import 'package:testapp/localization/app_localization.dart';
import 'package:testapp/pages/home.dart';
import 'package:testapp/pages/maison.dart';
import 'package:testapp/pages/map.dart';
import 'package:testapp/splash.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import 'package:testapp/providers/maisons_provider.dart';

void main() {
  LicenseRegistry.addLicense(() async* {
    final license = await rootBundle.loadString('google_fonts/OFL.txt');
    yield LicenseEntryWithLineBreaks(['google_fonts'], license);
  });

  runApp(
    SplashApp(
      key: UniqueKey(),
      onInitializationComplete: () => runMainApp(),
    ),
  );
}

void runMainApp() {
  runApp(
    MyApp(),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;

    return ChangeNotifierProvider(
      builder: (ctx) => MaisonsProvider(),
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Title',
        theme: ThemeData(
          primaryColor: Colors.black,
          scaffoldBackgroundColor: Color(0xFFDDCDC8),
          visualDensity: VisualDensity.adaptivePlatformDensity,
          textTheme: GoogleFonts.montserratTextTheme(textTheme).copyWith(
            headline1: GoogleFonts.montserrat(
              textStyle: textTheme.headline1,
              fontSize: 30,
              fontWeight: FontWeight.w600,
              color: Colors.black,
            ),
            headline2: GoogleFonts.montserrat(
              textStyle: textTheme.headline1,
              fontSize: 22,
              fontWeight: FontWeight.w500,
              color: Colors.black,
            ),
            bodyText1: GoogleFonts.montserrat(
              textStyle: textTheme.bodyText1,
              color: Colors.black,
              fontWeight: FontWeight.w400,
              fontSize: 19,
            ),
          ),
        ),
        home: BottomBar(),
        routes: {
          MaisonPage.routeName: (ctx) => MaisonPage(),
        },
        supportedLocales: [
          Locale('en', 'US'),
          Locale('it', 'IT'),
        ],
        localizationsDelegates: [
          AppLocalizations.delegate,
          GlobalMaterialLocalizations.delegate,
          GlobalWidgetsLocalizations.delegate,
        ],
      ),
    );
  }
}

class BottomBar extends StatefulWidget {
  @override
  _BottomBarState createState() => _BottomBarState();
}

class _BottomBarState extends State<BottomBar> {
  int _currentIndex = 0;

  final List<Widget> _children = [
    HomePage(),
    MapPage(),
  ];

  void onTabTapped(int index) {
    setState(() {
      _currentIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _children[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        backgroundColor: Color(0xFFDDCDC8),
        currentIndex: _currentIndex,
        onTap: onTabTapped,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.list),
            title: Text(
              AppLocalizations.of(context).translate('tab_maisons'),
            ),
          ),
          BottomNavigationBarItem(
            icon: Icon(
              Icons.map,
            ),
            title: Text(
              AppLocalizations.of(context).translate('tab_map'),
            ),
          ),
        ],
      ),
    );
  }
}

这是我的 Maison 模特。

import 'package:testapp/models/point.dart';
import 'package:flutter/material.dart';

class Maison {
  final String id;
  final String name;
  final String imageUrl;
  final String price;
  final Point coordinates;

  Maison({
    @required this.id,
    @required this.name,
    @required this.price,
    @required this.imageUrl,
    @required this.coordinates,
  });
}

这是提供商:

import 'package:testapp/models/point.dart';
import 'package:flutter/material.dart';
import '../models/maison.dart';

class MaisonsProvider with ChangeNotifier {
  // Questa lista è privata, non può essere recuperata dall'esterno, serve un getter
  List<Maison> _items = [
    Maison(
      id: '1',
      name: 'Prova 1',
      price: '60€',
      imageUrl:
          'https://images.unsplash.com/photo-1495562569060-2eec283d3391?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1500&q=80',
      coordinates: Point(
        lat: 45.4640976,
        lng: 9.1897378,
      ),
    ),
    Maison(
      id: '2',
      name: 'Prova 2',
      price: 'Free',
      imageUrl:
          'https://images.unsplash.com/photo-1582559934353-2e47140e7501?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1788&q=80',
      coordinates: Point(
        lat: 45.4640976,
        lng: 10.1897378,
      ),
    ),
    Maison(
      id: '3',
      name: 'Prova 3',
      price: 'Free',
      imageUrl:
          'https://images.unsplash.com/photo-1553355617-f7342d67a95f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1500&q=80',
      coordinates: Point(
        lat: 45.9,
        lng: 10.1897378,
      ),
    ),
    Maison(
      id: '4',
      name: 'Prova 4',
      price: '40€',
      imageUrl:
          'https://images.unsplash.com/photo-1555141816-810dd5692b6a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1789&q=80',
      coordinates: Point(
        lat: 46.4640976,
        lng: 9.30,
      ),
    ),
  ];

  // Getter
  // fa in modo che venga ritornata la lista di Maison,
  // non è la lista originale ma una copia.
  List<Maison> get items {
    return [..._items];
  }

  // Aggiunge una nuova Maison alla lista.
  // è importante passare dal provider così che sia lui a informare gli altri widget
  // che c'è stato un cambiamento
  void addMaison(Maison maison) {
    _items.add(maison);

    // Informa tutti i widget che sono interessati che c'è una nuova maison
    // in questo modo i widget possono fare una rebuild e mostrare i dati corretti
    notifyListeners();
  }
}

我已经添加了一个 addMaison 方法,它应该负责将一个新的 Maison 添加到我的 _item 和一个 getter 来检索它的副本。

我曾尝试以这种方式在 splash.dart 中初始化我的提供程序:

final maisonsData = Provider.of<MaisonsProvider>(context);
Maison m = Maison(
    id: '1',
    name: 'Prova add',
    imageUrl: 'https://upload.wikimedia.org/wikipedia/commons/1/17/Google-flutter-logo.png',
    coordinates: {
        lat: 23,
        lng: 23,
    }
);
maisonData.addMaison(m);

我尝试将它添加到我的 _initializeAsyncDependencies 但应用程序一直显示启动画面并且无法继续。
如何在 http 请求中使用提供程序?

提供程序是一个非常好的工具,无需调用它在树部分工作的 setstate 方法即可更新您的小部件树。
ChangeNotifier class :

in this class you can handle any logic of your app,in your case is sending request managing the data response.

ChangeNotifier 构造函数provider widget:

this is where you will initialize your change notifier class it should be upper in your widget tree, so child which depends of your logic code (Change notifier) get notified when some logic is done.

如何监听变化Consumer widget and provider.of<ChangeNotifierClass>(context):

these widget listen to notifyListeners() call inside your change notifier class so each time logic runs all the child widget which are listening to your ChangeNotifier the build method will run (so its basicly like calling setstate).

所以对于你的问题,你只需要围绕你的请求实现逻辑,从 json 数据和东西构建你的列表......。比 运行 通知监听器显示对该数据感兴趣的小部件的新状态。
对我来说,很难接受这样的逻辑,但有一次我清楚地看到它非常适合 flutter,如果需要更多帮助,你可以查看 bloc-pattern 架构。
希望它能帮助你好运。