I have created a reusable CircleAvatar widget which builds itself based on it's parameters. The parameters are using .watch() from riverpod & so update automatically. This translates to, in rough pseudo code:
No data (first/last name null && null avatar photo).....=> blank user icon avatar
Avatar image null && first/last name not null...........=> users initials in circle avatar
Avatar image not null...................................=> image avatar
Now I have three buttons, each button changes state which changes the parameters for the avatar as above. The buttons include futures like so:
Take picture.....=> image
Get photo file...=> image
Use initials.....=> image null (returns text initial avatar)
Problem I don't have a clue how to architect this into future builder. The "takePicture" & "getPhotoFile" methods need a future builder minimally since they take some time. I can't use FutureBuilder(future: [futureOne, futureTwo, futureThree])) because not all futures should be called at once...
To be clear, my code is working right now. But crashes on strange situation going back and forth from initial avatar to image avatar. And loading image takes some time.
How can something like this be designed into a future builder or similar concept?
Reactive avatar widget:
class ActiveAvatarCircle extends StatefulWidget {
const ActiveAvatarCircle({
Key? key,
required double? radius,
required String? initials,
required ImageProvider<Object>? avatar,
}) : _radius = radius,
_initials = initials,
_avatar = avatar,
super(key: key);
final double? _radius;
final String? _initials;
final ImageProvider<Object>? _avatar;
@override
State<ActiveAvatarCircle> createState() => _ActiveAvatarCircleState();
}
class _ActiveAvatarCircleState extends State<ActiveAvatarCircle> {
@override
Widget build(BuildContext context) {
// Default blank circle avatar - awaiting users initials
if (widget._initials == null && widget._avatar == null) {
return CircleAvatar(
backgroundColor: ThemeEndpoints.avatarBackgroundColor(),
foregroundColor: ThemeEndpoints.avatarForegroundColor(),
radius: widget._radius ?? 80,
child: ThemeEndpoints.avatarDefaultIcon(),
);
}
// Initialed circle avatar awaiting avatar choice/no choice
if (widget._initials != null && widget._avatar == null) {
return CircleAvatar(
radius: widget._radius ?? 80,
backgroundColor: ThemeEndpoints.avatarBackgroundColor(),
foregroundColor: ThemeEndpoints.avatarForegroundColor(),
child: Text(
widget._initials!,
style: ThemeEndpoints.avatarTextTheme(),
),
);
}
// Avatar selected and overriding both default & initials avatar
return CircleAvatar(
radius: widget._radius ?? 80,
foregroundImage: widget._avatar,
);
}
}
Avatar section in my form code:
// Avatar section
Widget _avatarSection(
SignUpState signUpState,
BuildContext context,
String? firstName,
String? lastName,
ImageProvider<Object>? avatar,
) {
return SizedBox(
height: 150,
child: SizedBox(
width: 90.w,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ActiveAvatarCircle(
radius: null,
initials: (firstName != null && lastName != null)
? '${firstName[0]}${lastName[0]}'
: null,
avatar: avatar,
),
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CustomButton(
buttonText: TextContent.of(context).authFormAvatarChoosePhotoText,
onPressed: () => _onGetImageFromFilesPressed(signUpState),
width: SizeConfig.screenWidth! * 0.40,
),
CustomButton(
buttonText: TextContent.of(context).authFormAvatarTakePhotoText,
onPressed: () => _onGetImageFromCameraPressed(signUpState),
width: SizeConfig.screenWidth! * 0.40,
),
CustomButton(
buttonText: TextContent.of(context).authFormAvatarInitialledText,
onPressed: () => _onUseInitialledAvatarPressed(signUpState),
width: SizeConfig.screenWidth! * 0.40,
),
],
),
],
),
),
);
}
// On get image from files pressed
Future<void> _onGetImageFromFilesPressed(SignUpState signUpState) async {
XFile? pickedFile = await ImagePicker().pickImage(
source: ImageSource.gallery,
maxWidth: 288,
maxHeight: 288,
imageQuality: 100,
);
if (pickedFile != null) {
final Uint8List bytes = await pickedFile.readAsBytes();
final MemoryImage image = MemoryImage(bytes);
signUpState.setAvatarStorageFormat(base64Encode(bytes));
signUpState.setAvatar(image);
pickedFile = null;
}
}
// On get get avatar from camera pressed
Future<void> _onGetImageFromCameraPressed(SignUpState signUpState) async {
XFile? cameraImage = await ImagePicker().pickImage(
source: ImageSource.camera,
maxWidth: 288,
maxHeight: 288,
imageQuality: 100,
);
if (cameraImage != null) {
final Uint8List bytes = await cameraImage.readAsBytes();
final MemoryImage image = MemoryImage(bytes);
signUpState.setAvatar(image);
signUpState.setAvatarStorageFormat(base64Encode(bytes));
}
}
// On use initialled avatar pressed
Future<void> _onUseInitialledAvatarPressed(SignUpState signUpState) async {
signUpState.setAvatar(null);
}