I'm trying to send a picture selected with image_picker from child to his parent, the child widget is PhotoPicker and the parent is UpdateInformationScreen. For that I've tried to use a callback but the File _deviceImage is null outside _pickImage method and I don't know why.
Inside _pickImage, _deviceImage is not null:
Future _pickImage(ImageSource source) async {
try {
XFile? pickedFile = await ImagePicker().pickImage(source: source);
if (pickedFile != null) {
setState(() {
_deviceImage = File(pickedFile.path);
print("_device 1: $_deviceImage"); // <--------------------------- Not null
});
}
} on PlatformException catch (e) {
Utils.showErrorMessage(e.message);
}
}
But in the onPressed it's null
TextButton.icon(
onPressed: () {
_pickImage(ImageSource.gallery);
widget.onImageSelected(_deviceImage);
print("_device 2: $_deviceImage"); // <--------------------------- Null
}, // => _pickImage(ImageSource.gallery),
icon: const Icon(
Icons.photo,
size: 30,
color: googleButtonTextColor,
),
label: const Text(
gallery,
style: TextStyle(
fontSize: 18,
color: googleButtonTextColor,
),
),
),
Child widget:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/constant/text.dart';
import 'package:flutter_test/utils/utils.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import '../constant/color.dart';
import '../model/instructor.dart';
class PhotoPicker extends StatefulWidget {
final Function(File?) onImageSelected;
const PhotoPicker({Key? key, required this.onImageSelected}) : super(key: key);
@override
State<PhotoPicker> createState() => _PhotoPickerState();
}
class _PhotoPickerState extends State<PhotoPicker> {
File? _deviceImage;
//----------------------------------------------------------------------------
//----------------------- Pick image from gallery or camera ------------------
//----------------------------------------------------------------------------
Future _pickImage(ImageSource source) async {
try {
XFile? pickedFile = await ImagePicker().pickImage(source: source);
if (pickedFile != null) {
setState(() {
_deviceImage = File(pickedFile.path);
print("_device 1: $_deviceImage"); <--------------------------- Not null
});
}
} on PlatformException catch (e) {
Utils.showErrorMessage(e.message);
}
}
@override
Widget build(BuildContext context) {
final instructor = ModalRoute.of(context)!.settings.arguments as Instructor;
return Column(
children: [
SizedBox(
child: _deviceImage != null
? CircleAvatar(
radius: 90,
backgroundImage: Image.file(
_deviceImage!,
fit: BoxFit.cover,
).image
)
: CircleAvatar(
radius: 90,
backgroundImage: instructor.photo != null
? NetworkImage(instructor.photo.toString())
: const AssetImage(
"assets/images/no_photo.jpg"
)
as ImageProvider,
),
),
const SizedBox(height: 20,),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton.icon(
onPressed: () {
_pickImage(ImageSource.gallery);
widget.onImageSelected(_deviceImage);
print("_device 2: $_deviceImage");
}, // => _pickImage(ImageSource.gallery),
icon: const Icon(
Icons.photo,
size: 30,
color: googleButtonTextColor,
),
label: const Text(
gallery,
style: TextStyle(
fontSize: 18,
color: googleButtonTextColor,
),
),
),
TextButton.icon(
onPressed: () {
_pickImage(ImageSource.camera);
widget.onImageSelected(_deviceImage); <--------------------------- Null
},//=> _pickImage(ImageSource.camera),
icon: const Icon(
Icons.photo_camera,
size: 30,
color: googleButtonTextColor,
),
label: const Text(
camera,
style: TextStyle(
fontSize: 18,
color: googleButtonTextColor,
),
),
),
],
),
],
);
}
}
The parent widget:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/utils/utils.dart';
import 'package:flutter_test/view/information_screen.dart';
import 'package:flutter_test/widget/photo_picker.dart';
import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:provider/provider.dart';
import '../constant/color.dart';
import '../constant/text.dart';
import '../model/instructor.dart';
import '../provider/instructor_view_model.dart';
class UpdateInformationScreen extends StatefulWidget {
static const String ROUTE_NAME = "/updateInformation";
const UpdateInformationScreen({Key? key}) : super(key: key);
@override
State<UpdateInformationScreen> createState() =>
_UpdateInformationScreenState();
}
class _UpdateInformationScreenState extends State<UpdateInformationScreen> {
final firstNameController = TextEditingController();
final phoneNumberController = TextEditingController();
String? phoneNumber;
File? photo;
//----------------------------------------------------------------------------------------------
//----------------------------- Free memory allocated to the existing variables ----------------
//----------------------------------------------------------------------------------------------
@override
void dispose() {
firstNameController.dispose();
phoneNumberController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final instructor = ModalRoute.of(context)!.settings.arguments as Instructor;
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pushNamed(context, InformationScreen.ROUTE_NAME);
},
),
title: const Text(appTitle),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20,),
PhotoPicker(
onImageSelected: (image) {
print("Image up: $image"); <--------------------------- Null
setState(() {
photo = image;
print("Image up set state: $photo");
});
},
),
const SizedBox(height: 20,),
//----------------------------- First name -------------------------
TextField(
controller: firstNameController,
cursorColor: cursorColor,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
prefixIcon: const Icon(
Icons.person,
color: grey,
size: 30,
),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.grey),
borderRadius: BorderRadius.circular(5.5),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.orange),
borderRadius: BorderRadius.circular(5.5),
),
labelText: instructor.firstName != null
? instructor.firstName.toString()
: updateFirstnameText,
),
),
const SizedBox(
height: 20,
),
//----------------------------- Phone number -----------------------
IntlPhoneField(
controller: phoneNumberController,
initialValue: instructor.phoneNumber != null
? Utils.phoneNumberWithoutCountryCode(instructor.phoneNumber.toString())
: phoneSignInLabelText,
decoration: InputDecoration(
labelText: instructor.phoneNumber != null
? Utils.phoneNumberWithoutCountryCode(instructor.phoneNumber.toString())
: phoneSignInLabelText,
border: const OutlineInputBorder(
borderSide: BorderSide(),
),
),
initialCountryCode: 'FR',
onChanged: (phone) {
phoneNumber = phone.completeNumber;
print(phone.completeNumber);
},
),
const SizedBox(
height: 20,
),
//----------------------------- Update button ----------------------
ElevatedButton(
onPressed: () {
_clickOnUpdateInformationButton(
context,
instructor.uid!,
//----------------------------- First name -----------------
firstNameController.text.isNotEmpty
? firstNameController.text.trim()
: instructor.firstName!,
//----------------------------- Phone number ---------------
phoneNumberController.text.isNotEmpty
? phoneNumber!
: instructor.phoneNumber!,
);
},
style: ElevatedButton.styleFrom(
fixedSize: const Size(double.infinity, 50),
primary: cursorColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
child: Row(
children: const [
Padding(
padding: EdgeInsets.only(left: 5, right: 100),
child: Icon(
Icons.update,
color: Colors.white,
size: 25,
),
),
Text(
updateInfoButton,
style: TextStyle(
fontSize: 18,
color: Colors.white,
),
),
],
),
),
const SizedBox(
height: 20,
),
],
),
),
);
}
//----------------------------------------------------------------------------
//----------------------------- Click on update information button -----------
//----------------------------------------------------------------------------
void _clickOnUpdateInformationButton(BuildContext context, String uid, String firstName, String phoneNumber) {
final instructorViewModel = Provider.of<InstructorViewModel>(context, listen: false);
instructorViewModel.updateInstructor(uid, firstName, phoneNumber);
Utils.goToInformationScreen(context);
Utils.showSuccessMessage(updateInformationSuccessText);
}
}
Thanks in advance
CodePudding user response:
Change onPressed
like this. The problem is image file picked inside method and ui is not waiting for this method complete it's job. await
keyword used to wait for operation result. For more, search about asynchronous programming in dart.
onPressed: () async {
await _pickImage(ImageSource.gallery);
widget.onImageSelected(_deviceImage);
print("_device 2: $_deviceImage"); // <--------------------------- Null
}, // => _pickImage(ImageSource.gallery),