【发布时间】: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 中的使用,以找出我做错了什么。
【问题讨论】: