0

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);
  }
RobbB
  • 1,214
  • 11
  • 39
  • I can't get exactly the output that you are trying to achieve. but if I understood better, you are trying to update the circle avatar to use the image taken by the user and use the default if no image is taken am I correct? – john Oct 16 '22 at 04:18
  • Yes that is correct, the code is working and should work copy/past into a dart pad. But I need to figure out a way to do it all with a future builder or some such configuration. – RobbB Oct 16 '22 at 04:23
  • My apologize, I just realized that it's not totally copy/past - able because of my reusable buttons and theme data. – RobbB Oct 16 '22 at 04:25
  • on your _onGetImage functions you are saving the image directly to somewhere else so inside your build widget you should be streaming the database where you are saving the image so that it gets rebuilt once it detects a change. – john Oct 16 '22 at 08:04

0 Answers0