Search code examples
flutterflutter-getx

getx obx not updating avatar image - Flutter GetX


what I want to achieve is to change the image in CircleAvatar when I'm selecting an image, here is the code:

ProfileController:

class ProfileController extends GetxController {
  final TextEditingController emailController = TextEditingController();
  final ImagePicker _picker = ImagePicker();
  Rx<String?> avatarPath = null.obs;


  avatarFromCamera() async {
    var localAvatar = await _picker.pickImage(
        source: ImageSource.camera, imageQuality: 50
    );

    if (localAvatar != null) {
      avatarPath = localAvatar.path.obs;
      update();
    }
  }

  avatarFromGallery() async {
    var localAvatar = await  _picker.pickImage(
        source: ImageSource.gallery, imageQuality: 50
    );

    if (localAvatar != null) {
      avatarPath = localAvatar.path.obs;
      update();
    }
  }

 String? emailValidator(String? value) {
    if (value == null || value.isEmpty) {
      return null;
    }

    if (!EmailValidator.validate(value, false)) {
      return 'Invalid email address';
    }
  }

  @override
  void onClose() {
    emailController.dispose();
    super.onClose();
  }

  String? emailValidator(String? value) {
    if (value == null || value.isEmpty) {
      return null;
    }

    if (!EmailValidator.validate(value, false)) {
      return 'Invalid email address';
    }
  }

 void save(GlobalKey<FormState> profileFormKey) {
    if (profileFormKey.currentState!.validate()) {
      print('valid');
    }
  }
}

and here is the ProfileScreen widget:

lass ProfileScreen extends StatelessWidget {
  final ProfileController _profileController = Get.put<ProfileController>(ProfileController());
  GlobalKey<FormState> profileFormKey = GlobalKey<FormState>();

  ProfileScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Update user details'),
      ),
      body: SingleChildScrollView(
        child: Form(
          key: profileFormKey,
          child: Column(
          children: [
            Padding(
              padding: const EdgeInsets.all(30.0),
              child: TextFormField(
                keyboardType: TextInputType.text,
                controller: _profileController.emailController,
                decoration: const InputDecoration(
                  labelText: 'Enter email',
                ),
                validator: _profileController.emailValidator,
              ),
            ),
            Center(
              child: GestureDetector(
                onTap: () {
                  showModalBottomSheet(
                      context: context,
                      builder: (BuildContext bc) {
                        return SafeArea(
                          child: Wrap(
                            children: <Widget>[
                               ListTile(
                                  leading: const Icon(Icons.photo_library),
                                  title: const Text('Photo Library'),
                                  onTap: () {
                                    _profileController.avatarFromGallery();
                                    Navigator.of(context).pop();
                                  }),
                               ListTile(
                                leading: const Icon(Icons.photo_camera),
                                title: const Text('Camera'),
                                onTap: () {
                                  _profileController.avatarFromCamera();
                                  Navigator.of(context).pop();
                                },
                              ),
                            ],
                          ),
                        );
                      }
                  );
                },
                child:  CircleAvatar(
                  radius: 55,
                  backgroundColor: Colors.pink,
                  child: Obx(() =>(_profileController.avatarPath.value != null)
                      ? ClipRRect(
                    borderRadius: BorderRadius.circular(50),
                    child: Image.file(
                      File(_profileController.avatarPath.value!),
                      width: 100,
                      height: 100,
                      fit: BoxFit.fitHeight
                    ),
                  )
                      : Container(
                    decoration: BoxDecoration(
                        color: Colors.grey[200],
                        borderRadius: BorderRadius.circular(50)),
                    width: 100,
                    height: 100,
                    child: Icon(
                      Icons.camera_alt,
                      color: Colors.grey[800],
                    ),
                  ),
                ),
              ),
            )),
            Container(
              margin: const EdgeInsets.all(10),
              width: double.infinity,
              child: MaterialButton(
                color: Colors.blue,
                onPressed: () => _profileController.save(profileFormKey),
                child: const Text(
                  'Submit',
                  style: TextStyle(color: Colors.white),
                ),
              ),
            ),
          ],
        ),
        ),
      ),
    );
  }
}

as you can see, I have Obx and my avatarPath reactive, and I'm running update everywhere I changing it, but it's not udpated. I also tried to use empty string as initial value of imagePath like this Rx<String> avatarPath = ''.obs; and it's not working. What I'm doing wrong??? Thank you in advice!!!


Solution

  • There are two things to revise for it. Firstly, change avatarPath = localAvatar.path.obs; with avatarPath.value = localAvatar.path;. Because localAvatar.path.obs create the new observable and changes will not be reflected to previous observers.

    Secondly, create a new stateless widget having the widget tree of bottom sheet's builder like

    showModalBottomSheet(
      context: context,
      builder: (BuildContext bc) {
            return CustomBottomView();
      }
    );
    

    Then inside the CustomBottomView copy your bottom sheet widget tree.

    class CustomBottomView extends GetView<ProfileController> {
      return YourBottomSheetWidgetTreeHere();
    }
    

    Dont worry about ProfileController here. You have already put it in DI in previous route. First follow the first step if you still face the problem second step will definitely resolve it.