How can I build a widget conditionally, based on various different futures that could be called from a few buttons?

57 Views Asked by At

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);
  }
0

There are 0 best solutions below