Clarification about provider in Flutter – How to save data

Issue

I’m new to Flutter and I’m spending some time about provider patter, I’m a PHP guy so I have a basic knowledge about it works, especially in Laravel framework.
For me a provider is something that should manage data, retrive it from a DB, manipulate ad save.

As far as I understand the concept is the same in flutter, but is quite difficult follow the docs.

I have created a simple app that call a REST API and now I’d like to add the response to my provider to use the data in all my pages and widgets.

Here my code.

The splash.dart file that show a splashscreen to my user and call the external endpoint, here I’d like to get my data and add to the provider.

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'),
                  ],
                ),
              ),
            ],
          )
        ],
      ),
    );
  }
}

Here’s my main.dart file, here I init my tabs and some stuff. I have added there the ChangeNotifyer because here I need the data, maybe is not the right place?

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'),
            ),
          ),
        ],
      ),
    );
  }
}

This is my Maison model.

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,
  });
}

And here’s the provider:

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();
  }
}

I have added a addMaison method that should be the responsable to add a new Maison to my _item and a getter to retrive a copy of it.

I have tried to init my provider in splash.dart in this way:

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);

and I tried to add it in my _initializeAsyncDependencies but the apps keep showing the splashscreen and can’t continue.
How can I use the provider along the http request?

Solution

providers are a really good tool to update your widget tree without calling the setstate method it work in tree part.
the ChangeNotifier class :

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

The ChangeNotifier constructor 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.

How to listen to changes 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).

So for your question you just need to implement the logic around your request building your lists from json data and stuff… . than running notifylisteners to display and new state of your widgets which are interest in that data .
For me it was pretty hard to accept such logic but after done that one time i clearly saw that fit pretty well with flutter , and for more help you can check the bloc-pattern architecture .
hope it helped you good luck .

Answered By – Kadri Nadir

Answer Checked By – Timothy Miller (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *