【问题标题】:Correct use of Flutter Provider needed需要正确使用 Flutter Provider
【发布时间】:2021-05-31 19:09:06
【问题描述】:

我正在开发一个 Flutter 应用程序。有一个登录用户想要更改他的个人资料图片。 我正在使用远程 MySQL 数据库来存储所有用户信息,例如电子邮件、个人资料图片和用户 ID。 登录后,所有用户信息都使用共享首选项保存。 现在,用户想要更改他的个人资料图片并打开屏幕更改个人资料图片 (cambiar_foto_perfil.dart)。 在该屏幕上,有一个 image_picker 和一个按钮 onPressed 操作,用于将新图片上传到远程服务器,以更新远程数据库上的新头像。

当用户更改他的个人资料图片时,我正在使用 Provider 来保持整个应用程序的更新。同一个提供者类正在更新共享首选项。

这是 user_provider.dart:

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

class UsuarioProvider with ChangeNotifier{

  String _imagen;



  usuarioProvider(){
    _imagen = 'No imagen';

    loadPreferences();
  }

  //getters

  String get imagen => _imagen;


  //setters
  void setimagen(String imagen){

    _imagen = imagen;

    notifyListeners();
    savePreferences();
  }


  loadPreferences() async {

    SharedPreferences prefs = await SharedPreferences.getInstance();
    String imagen = prefs.getString('foto');

    if (_imagen != null) setimagen(imagen);


  }
  savePreferences() async {

    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString('foto', _imagen);

  }

}

我在小部件树的顶层实例化提供程序,如下在 main.dart 中:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  SharedPreferences prefs = await SharedPreferences.getInstance();
  var email = prefs.getString('email');

  print(email);

  runApp(
    EasyLocalization(
      child: MultiProvider(
        providers: [
          ChangeNotifierProvider(create: (BuildContext context) => ClinicaProvider()),
          ChangeNotifierProvider(create: (BuildContext context) => UsuarioProvider()),
        ],

          child: MaterialApp(
              debugShowCheckedModeBanner: false,
              home: email == null || email == '' ? Splash2() : HomeScreen())),
      path: "assets/translations",
      saveLocale: true,
      supportedLocales: [Locale('en', 'EN'), Locale('es', 'ES')],
    ),
  );
}

现在,当用户想要更改他的个人资料图片时,会调用 cambiar_foto_perfil.dart 类。这里有课程代码:

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_capenergy/classes/user.dart';
import 'package:flutter_capenergy/providers/user_provider.dart';
import 'package:flutter_capenergy/servicios/chech_internet.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'package:easy_localization/easy_localization.dart';
import 'package:toast/toast.dart';

class CambiarFotoPerfil extends StatefulWidget {
  @override
  _CambiarFotoPerfilState createState() => _CambiarFotoPerfilState();
}

class _CambiarFotoPerfilState extends State<CambiarFotoPerfil> {
  File _selectedFile;
  bool _inProcess = false;
  final _picker = ImagePicker();

  String miEmail = "";
  String miTel = "";
  String miUltimoEmail = "";
  String miImagen = "";
  String miId = "";
  String _textoInfo = "";
  bool isLoading = false;

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

    getEmailUsuarioActual();
    getTelUsuarioActual();
    getImagenUsuarioActual();
    getIdUsuarioActual();
    getUltimoEmailUsuarioActual();
    checkInternet().checkConnection(context, "YouAreConnected".tr().toString(),
        "YouAreNotConnected".tr().toString(), "WaitConnection".tr().toString());
  }

  @override
  void dispose() {
    super.dispose();
    print("run dispose");
  }

  Future<String> getEmailUsuarioActual() async {
    final prefs = await SharedPreferences.getInstance();

    miEmail = prefs.getString("email");

    setState(() {});
  }

  Future<String> getTelUsuarioActual() async {
    final prefs = await SharedPreferences.getInstance();

    miTel = prefs.getString("tel");

    setState(() {});
  }

  Future<String> getUltimoEmailUsuarioActual() async {
    final prefs = await SharedPreferences.getInstance();

    miUltimoEmail = prefs.getString("ultimo_email");

    setState(() {});
  }

  Future<String> getImagenUsuarioActual() async {
    final prefs = await SharedPreferences.getInstance();
    miImagen = prefs.getString("foto");

    setState(() {});
  }

  Future<String> getIdUsuarioActual() async {
    final prefs = await SharedPreferences.getInstance();
    miId = prefs.getString("id");

    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    localizationsDelegates:
    context.localizationDelegates;
    supportedLocales:
    context.supportedLocales;
    locale:
    context.locale;

    var usuarioProvider = Provider.of<UsuarioProvider>(context);

    //changes profile picture in the database
    Future<User> cambiarFotoUsuario() async {
      setState(() {
        isLoading = true;
      });

      var url =
          "https://app.com/flutter_api/cambiar_foto_usuario.php";
      final response =
          await http.post(url, body: {"id": miId, "foto": miImagen});

      print("respuesta :" + response.body);

      print(response.statusCode);

      final Map parsed = json.decode(response.body);

      setState(() {
        isLoading = false;
      });
    }

    final String phpEndPoint =
        'https://app.com/administrar/application/admin/usuarios/subir_foto_perfil.php';

    //uploads profile picture to the server
    void _upload(File file) {
      if (file == null) return;
      setState(() {
        _textoInfo = "Subiendo foto al servidor...";
      });
      String base64Image = base64Encode(file.readAsBytesSync());
      String fileName = file.path.split("/").last;

      http.post(phpEndPoint, body: {
        "image": base64Image,
        "name": fileName,
      }).then((res) async {
        print(res.statusCode);
        setState(() {
          _textoInfo = "Foto del perfil actualizada";
          miImagen = fileName;
        });

        // updates provider with new profile image
        usuarioProvider.setimagen(fileName);

        cambiarFotoUsuario();

      }).catchError((err) {
        print(err);
      });
    }

    Widget getImageWidget() {
      if (_selectedFile != null) {
        return Image.file(
          _selectedFile,
          width: 220,
          height: 220,
          fit: BoxFit.cover,
        );
      } else {
        return Image.asset(
          "assets/images/placeholder.png",
          width: 220,
          height: 220,
          fit: BoxFit.cover,
        );
      }
    }

    getImage(ImageSource source) async {
      this.setState(() {
        _inProcess = true;
      });
      File image = await ImagePicker.pickImage(source: source);
      if (image != null) {
        File cropped = await ImageCropper.cropImage(
            sourcePath: image.path,
            aspectRatio: CropAspectRatio(ratioX: 1, ratioY: 1),
            compressQuality: 100,
            maxWidth: 700,
            maxHeight: 700,
            compressFormat: ImageCompressFormat.jpg,
            androidUiSettings: AndroidUiSettings(
              toolbarColor: Colors.deepOrange,
              toolbarTitle: "RPS Cropper",
              statusBarColor: Colors.blueAccent,
              backgroundColor: Colors.white,
            ));

        this.setState(() {
          _selectedFile = cropped;
          print(_selectedFile.toString());
          _inProcess = false;
        });
      } else {
        this.setState(() {
          _inProcess = false;
        });
      }
    }

    return Scaffold(
        appBar: AppBar(
          centerTitle: false,
          title: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                "Capenergy",
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
              ),
              Visibility(
                visible: true,
                child: Text(
                  miEmail,
                  style: TextStyle(
                    fontSize: 12.0,
                  ),
                ),
              ),
            ],
          ),
          actions: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: CircleAvatar(
                radius: 25.0,
                backgroundImage: NetworkImage(
                    'https://app.com/administrar/application/admin/usuarios/' +
                        usuarioProvider.imagen),
                backgroundColor: Colors.transparent,
              ),
            )
          ],
        ),
        body: Stack(
          children: <Widget>[
            Container(
              child: Padding(
                padding: EdgeInsets.all(20.0),
                child: Column(
                  children: <Widget>[
                    SizedBox(height: 20),
                    Text(
                      "Selecciona la foto para tu perfil de usuario ",
                      style: TextStyle(fontSize: 24.0, color: Colors.black45),
                    ),
                  ],
                ),
              ),
            ),
            Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                SizedBox(height: 60),
                getImageWidget(),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: <Widget>[
                    IconButton(
                      onPressed: () {
                        getImage(ImageSource
                            .camera); //  You enter here what you want the button to do once the user interacts with it
                      },
                      icon: Icon(
                        Icons.add_a_photo_rounded,
                        color: Colors.green,
                        size: 62.0,
                      ),
                      iconSize: 20.0,
                    ),
                    IconButton(
                      onPressed: () {
                        getImage(ImageSource.gallery);
                        ; //  You enter here what you want the button to do once the user interacts with it
                      },
                      icon: Icon(
                        Icons.image_search_outlined,
                        color: Colors.lightBlueAccent,
                        size: 62.0,
                      ),
                      iconSize: 20.0,
                    ),
                  ],
                ),
                SizedBox(height: 20),
                RaisedButton.icon(
                  onPressed: () {
                    _upload(_selectedFile);
                    print('Button Clicked.');
                  },
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.all(Radius.circular(10.0))),
                  label: Text(
                    'Selecciona la foto para tu perfil',
                    style: TextStyle(color: Colors.white),
                  ),
                  icon: Icon(
                    Icons.track_changes,
                    color: Colors.white,
                  ),
                  textColor: Colors.white,
                  splashColor: Colors.red,
                  color: Colors.blueAccent,
                ),
                Text(_textoInfo),
              ],
            ),
            (_inProcess)
                ? Container(
                    color: Colors.white,
                    height: MediaQuery.of(context).size.height * 0.95,
                    child: Center(
                      child: CircularProgressIndicator(),
                    ),
                  )
                : Center()
          ],
        ));
  }
}

这里有一个来自该屏幕的屏幕截图:

如果用户点击相机按钮,他可以拍摄一张照片,然后显示在占位符空间上。如果用户单击搜索按钮并从设备的图库中选择一张图片,也会发生同样的情况,然后所选图片会显示在占位符空间中。

一旦拍摄或选择了图片,如果用户点击蓝色按钮,图片就会上传到服务器并更新远程数据库。所有这些功能都按预期工作。 另一方面,我想更新共享首选项,并希望提供者通知所有其他树小部件用户个人资料图片已更改。

但是在单击蓝色按钮之后,我在日志控制台上收到了这条异常消息:

======== Exception caught by foundation library ====================================================
The following assertion was thrown while dispatching notifications for UsuarioProvider:
setState() or markNeedsBuild() called during build.

This _InheritedProviderScope<UsuarioProvider> widget cannot be marked as needing to build because the framework is already in the process of building widgets.  A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: _InheritedProviderScope<UsuarioProvider>
  value: Instance of 'UsuarioProvider'
  listening to value
The widget which was currently being built when the offending call was made was: HomeScreen
  dirty
  dependencies: [_InheritedProviderScope<UsuarioProvider>, MediaQuery, _EasyLocalizationProvider, _InheritedProviderScope<ClinicaProvider>]
  state: _HomeScreenState#ce28a
When the exception was thrown, this was the stack: 
#0      Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:4292:11)
#1      Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:4307:6)
#2      _InheritedProviderScopeElement.markNeedsNotifyDependents (package:provider/src/inherited_provider.dart:491:5)
#3      ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:226:25)
#4      UsuarioProvider.setimagen (package:flutter_capenergy/providers/user_provider.dart:26:5)
...
The UsuarioProvider sending notification was: Instance of 'UsuarioProvider'
====================================================================================================

请查看我的代码以检查提供程序类、提供程序实例化和提供程序在 cambiar_foto_perfil.dart 中的使用,以找出我做错了什么。

【问题讨论】:

    标签: flutter flutter-provider


    【解决方案1】:

    无需使用setState(),使用Consumer()或Provider.of获取Model的值,Consumer()在调用notifyListeners()时监听并重建UI

    调用提供者方法

    Provider.of<usuarioProvider>(context, listen: false).setimagen("name_of_image"); 
    

    要获取提供者值,请更改您的代码

                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: CircleAvatar(
                    radius: 25.0,
                    backgroundImage: NetworkImage(
                        'https://app.com/administrar/application/admin/usuarios/' +
                            usuarioProvider.imagen),
                    backgroundColor: Colors.transparent,
                  ),
    

    Consumer<usuarioProvider>(
        builder: (context, provider, _) {
                return Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: CircleAvatar(
                    radius: 25.0,
                    backgroundImage: NetworkImage(
                        'https://app.com/administrar/application/admin/usuarios/' +
                            provider.imagen),
                    backgroundColor: Colors.transparent,
                  ),
        }
    )
    

    【讨论】:

    • 您介意向我展示在构建方法中更新提供程序的正确方法吗?我正在使用 usuarioProvider.setimagen("name of the image"),但是这样会抛出异常
    猜你喜欢
    • 2020-11-29
    • 2021-01-08
    • 2020-06-03
    • 2022-08-12
    • 2020-05-26
    • 2021-11-13
    • 2021-01-04
    • 1970-01-01
    • 2021-07-16
    相关资源
    最近更新 更多