currently I did applied new http POST request to upload profile picture. I have several pages that needed the profile picture such as profile page and my details page. right now, every time I upload new profile picture, the profile picture in my details page not updating right away. I need to navigate back to profile page and come back to my details page then the profile picture updated. I have been stuck for 5 days doing this things. can anyone help me?
currently, this is my implementation. I have tried to use blocbuilder inside the Stack but after upload new image, it just keep reloading. need to go back to home page navigate to profile page then my details page then it will working fine. i also tried to change the NetworkImage() into FileImage() if image != null but then I got this error Cannot retrieve length of file, path = ‘https://abcde.my/0/12/1’ (OS Error: No such file or directory, errno = 2). i also tried to change the way im structuring pickImageFromSource() but yeah still the same, the profile picture not right away updating.
Im expecting for the profile picture to right away updating after user pick new image. no need to go to profile page to get the latest profile picture in my details page.
API service
Future<http.Response> uploadProfilePicture(File? file) async {
final List<int> imageBytes = file!.readAsBytesSync();
final base64Image = base64Encode(imageBytes);
final userLoginToken = await AppSharedPreferences.getUserLoginToken();
try {
final timeStamp = DateTimeHelper().apiRequiredTimeStamp();
final signature = await EncryptionService.apiSignature(
apiCodeName: 'UploadProfilePic',
timeStamp: timeStamp,
hasLoginToken: true);
var body = {
"Channel": "B",
"LoginToken": userLoginToken,
"ProfileImgFileContent": base64Image,
"ProfileImgFileName":
file.path.split('/').last, // Use the file name from the path
"ProfileImgFileSize": imageBytes.length.toString(),
"ProfileImgFileType":
'image/${file.path.split('.').last.toLowerCase()}', // Use the file extension for type
};
final response = await http.post(
Uri.parse('${AppConfig.development().apiBaseUrl}/UploadProfilePic'),
headers: {
"Content-Type": "application/json"
},
body: jsonEncode(body),
);
print('upload pp timestamp: $timeStamp');
print('upload pp signature: $signature');
print('upload pp response status code: ${response.statusCode}');
return response;
} catch (error) {
throw Exception('Failed to update profile picture.');
}
}
Repository
class ProfileContentRepository {
static final ProfileContentRepository _profileContentRepository =
ProfileContentRepository._internal();
factory ProfileContentRepository() {
return _profileContentRepository;
}
ProfileContentRepository._internal();
final apiService = ApiService();
Future<http.Response> uploadProfilePicture(File? file) async {
return apiService.uploadProfilePicture(file);
}
}
Inside the Cubit
Future<void> uploadProfilePicture(File? image) async {
final apiResponse =
await profileContentRepository.uploadProfilePicture(image);
if (apiResponse.statusCode == 200) {
print('Profile picture uploaded successfully');
fetchProfileContent();
} else {
emit(ProfileErrorState(
error:
'Failed to upload profile picture: ${apiResponse.statusCode} - ${apiResponse.body}'));
}
}
the UI part
class ProfilePageContent extends StatelessWidget {
final MemberProfile memberProfile;
const ProfilePageContent({
Key? key,
required this.memberProfile,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return MyDetails(
memberProfile: memberProfile,
);
}
}
class MyDetails extends StatefulWidget {
const MyDetails({
required this.memberProfile,
Key? key,
}) : super(key: key);
final MemberProfile memberProfile;
@override
State<MyDetails> createState() => __MyDetailsState();
}
class __MyDetailsState extends State<MyDetails> {
final _formKey = GlobalKey<FormState>();
File? image;
bool isLoading = false;
late ProfileContentCubit profileContentCubit;
@override
void initState() {
profileContentCubit = BlocProvider.of<ProfileContentCubit>(context);
getMemberInfo();
super.initState();
}
getMemberInfo() async {
setState(() {
isLoading = true;
});
image = widget.memberProfile.memberInfo!.profileImage != ""
? File(widget.memberProfile.memberInfo!.profileImage!)
: null;
setState(() {
isLoading = false;
});
}
Future pickImage() async {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Choose Image Source'),
actions: [
TextButton(
onPressed: () async {
Navigator.of(context).pop();
await pickImageFromSource(ImageSource.gallery);
},
child: const Text('Gallery'),
),
TextButton(
onPressed: () async {
Navigator.of(context).pop();
await pickImageFromSource(ImageSource.camera);
},
child: const Text('Camera'),
),
],
);
},
);
}
Future<void> pickImageFromSource(ImageSource source) async {
try {
final pickImg = await ImagePicker().pickImage(source: source);
print('pickIMG $pickImg');
print('BEFOREEE $image');
if (pickImg != null) {
// Perform the asynchronous work outside of setState
final pickedImage = File(pickImg.path);
print('Picked Image Path: ${pickedImage.path}');
// Update the state synchronously
context.read<ProfileContentCubit>().uploadProfilePicture(pickedImage);
setState(() {
evictImage();
image = pickedImage;
print('AFTERR $image');
});
} else {
const Text('No photo was selected or taken');
}
} on PlatformException {
throw Exception('Cannot pick image!');
}
}
void evictImage() {
final NetworkImage provider =
NetworkImage(widget.memberProfile.memberInfo!.profileImage!);
provider.evict().then<void>((bool success) {
if (success) {
debugPrint('CACHEEEEEE removed image!');
}
});
}
@override
Widget build(BuildContext context) {
updateMemberInfo() {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
context
.read<ProfileContentCubit>()
.updateMemberInfo(widget.memberProfile.memberInfo!);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text(
'Profile Updated',
style: TextStyle(
color: Colors.black,
),
),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
);
}
}
return Scaffold(
body: isLoading
? const Center(
child: CircularProgressIndicator(),
)
: SingleChildScrollView(
child: Container(
padding:
const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
child: Column(
children: [
Stack(
children: [
image != null
? SizedBox(
width: 120,
height: 120,
child: CircleAvatar(
radius: 100,
backgroundImage: NetworkImage(widget
.memberProfile.memberInfo!.profileImage!),
),
)
: SizedBox(
width: 120,
height: 120,
child: CircleAvatar(
backgroundColor: Colors.green.shade200,
radius: 100,
child: Text(
widget.memberProfile.memberInfo!.fullName
.trim()[0],
style: const TextStyle(
fontSize: 70,
),
),
),
),
Positioned(
bottom: 0,
right: 0,
child: InkWell(
onTap: pickImage,
child: Container(
width: 35,
height: 35,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: Theme.of(context)
.colorScheme
.inversePrimary),
child: const Icon(Icons.camera_alt_rounded,
color: Colors.black, size: 20),
),
),
),
],
),
2
Answers
Does
fetchProfileContent()
in your Cubit emit the new state ? It’s not part of the code you’ve posted.If not, that will be your problem, since you’re updating the image on the server and on status == 200 you’re not emitting the updated state to the app.
In this if in your cubit you do not emit new state on
statusCode==200
.Try something like:
Of course if you have ProfileSuccessState created