首页 > 解决方案 > Flutter中关于provider的说明——如何保存数据

问题描述

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

据我了解,颤振中的概念是相同的,但遵循文档非常困难。

我创建了一个调用 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 请求中使用提供程序?

标签: flutterflutter-providerflutter-http

解决方案


提供程序是一个非常好的工具,可以在不调用它在树部分工作的 setstate 方法的情况下更新小部件树。
ChangeNotifier 类:

在此类中,您可以处理应用程序的任何逻辑,在您的情况下是发送管理数据响应的请求。

ChangeNotifier 构造函数provider widget

这是您将初始化更改通知器类的地方,它应该在您的小部件树中位于上层,因此当某些逻辑完成时,取决于您的逻辑代码(更改通知器)的子级会收到通知。

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

这些小部件notifyListeners()在您的更改通知程序类中监听调用,因此每次逻辑运行所有正在监听您的 ChangeNotifier 的子小部件时,构建方法都会运行(因此它基本上就像调用 setstate)。

因此,对于您的问题,您只需要围绕您的请求实现逻辑,从 json 数据和内容构建您的列表...。而不是运行 notifylisteners 来显示您对该数据感兴趣的小部件的新状态。
对我来说,很难接受这样的逻辑,但有一次我清楚地看到它非常适合颤振,如果需要更多帮助,你可以检查 bloc-pattern 架构。
希望它帮助你好运。


推荐阅读