需要正确使用 Flutter Provider

Correct use of Flutter Provider needed

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

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

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

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

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

这里是这个屏幕的截图:

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

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

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

======== 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'
====================================================================================================

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

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

调用提供者方法

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