felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.8k stars 3.39k forks source link

How to pass data to another screen with bloc -Flutter #2873

Closed Oliwiana closed 3 years ago

Oliwiana commented 3 years ago

How to transfer data from one screen to another using bloc and save , I would like to create a user profile where I have two screens, two steps to creating a profile. I created two blocs for each class, in one I have an avatar, city and name, in the other only description. I used an amplify and when I save the first screen and when I go to the second one, delete the data from the first screen. How do I save everything? without delete? after save second screen.

First screen:

 class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
 final DataRepository dataRepo;
 final StorageRepository storageRepo;
 final _picker = ImagePicker();

ProfileBloc(
  {User? user,
  required bool isCurrentUser,
  required this.storageRepo,
  required this.dataRepo})
  : super(ProfileState(user: user, isCurrentUser: isCurrentUser)) {
// storageRepo
//     .getUrlForFile(user!.avatarKey)
//     .then((url) => add(ProvideImagePath(avatarPath: url)));

ImageUrlCache.instance
    .getUrl(user!.avatarKey)
    .then((url) => add(ProvideImagePath(avatarPath: url)));
}

@override
Stream<ProfileState> mapEventToState(ProfileEvent event) async* {
if (event is ChangeAvatarRequest) {
  yield state.copyWith(isImageSourceActionSheetVisible: true);
} else if (event is OpenImagePicker) {
  yield state.copyWith(isImageSourceActionSheetVisible: false);
  try {
    final selectedImage =
        await _picker.pickImage(source: event.imageSource);
    if (selectedImage == null) return;
    final imageKey = await storageRepo.uploadFile(File(selectedImage.path));
    final user = state.user!.copyWith(avatarKey: imageKey);

    String? imageUrl;
    await Future.wait<void>([
      dataRepo.updateUser(user),
      storageRepo.getUrlForFile(imageKey).then((value) => imageUrl = value)
    ]);

    yield state.copyWith(avatarPath: imageUrl);
  } catch (e) {
    throw e;
  }
} else if (event is ProvideImagePath) {
  if (event.avatarPath != null)
    yield state.copyWith(avatarPath: event.avatarPath);
} else if (event is ProfileCityChanged) {
  yield state.copyWith(userCity: event.city);
} else if (event is ProfileNameChanged) {
  yield state.copyWith(userName1: event.name);
} else if (event is SaveProfileChanges) {
  // handle save changes
  yield state.copyWith(formStatus: FormSubmitting());
  final updatedUser2 =
      state.user!.copyWith(city: state.userCity, name: state.userName1);
  try {
    await dataRepo.updateUser(updatedUser2);
    print(updatedUser2);
    yield state.copyWith(formStatus: SubmissionSuccess());
  } on Exception catch (e) {
    yield state.copyWith(formStatus: SubmissionFailed(e));
  } catch (e) {
    print(e);
  }
}
}
}

Second screen:

class Profile2Bloc extends Bloc<Profile2Event, Profile2State> {
 final DataRepository dataRepo;
// User? user;
//   String ?get userCity => user!.city;
//   String? get userName1 => user!.name;

Profile2Bloc(
    {User? user, required bool isCurrentUser, required this.dataRepo})
     : super(Profile2State(user: user, isCurrentUser: isCurrentUser));

 @override
 Stream<Profile2State> mapEventToState(Profile2Event event) async* {
  if (event is ProfileDescriptionChanged) {
    yield state.copyWith(userDescription: event.description);
   } else if (event is SaveProfile2Changes) {
    yield state.copyWith(formStatus: FormSubmitting());
  final updatedUser =
  state.user!.copyWith(description: state.userDescription);
  try {
    await dataRepo.updateUser(updatedUser);
    print(updatedUser);
    // print(userDescribe);
    yield state.copyWith(formStatus: SubmissionSuccess());
  } on Exception catch (e) {
    yield state.copyWith(formStatus: SubmissionFailed(e));
  } catch (e) {
    print(e);
  }
}
}
}

Data Repo:

 Future<User> updateUser(User updatedUser) async {
   try {
     await Amplify.DataStore.save(updatedUser);
     return updatedUser;
    } catch (e) {
       throw e;
      }
      }
marcossevilla commented 3 years ago

hi @Oliwiana! I suggest you have only one ProfileBloc in your case. That way, you'll be able to fill in the user data from a page on which you can have a form and a separate bloc to handle the form submission logic, like LoginBloc for example, and then you consume ProfileBloc in any page you want.

I have an example of filling a sign-up form and showing a profile page with the data of that form here. It's done with flow_builder and formz but the logic is similar to what you want to achieve.

Hope that helps. πŸ‘

Oliwiana commented 3 years ago

thanks for answer!

Going from one screen to the next keeps repeating causing the deletion of the data from the first screen, how to save everything from the first and the second using one block using amplify?

  Future<User> updateUser(User updatedUser) async {
       try {
         await Amplify.DataStore.save(updatedUser);
          return updatedUser;
            } catch (e) {
                 throw e;
                  }
              }
           User? user;
         String ?get userCity => user!.city;
       String? get userName1 => user!.name;
       Future<User> updateUser2(User newData) async {
        String? userId;
         User oldData = (await Amplify.DataStore.query(User.classType,
         where: User.ID.eq(userId!)))
         [0];
      User newData= oldData.copyWith(city: userCity,name: userName1);
       try {
          await Amplify.DataStore.save(newData);
            return newData;
               } catch (e) {
              throw e;
                  }
                       }

I changed everything and now I have one bloc of Profile:

            class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
                final DataRepository dataRepo;
           final StorageRepository storageRepo;
           final _picker = ImagePicker();

          ProfileBloc(
            {User? user,
            required bool isCurrentUser,
            required this.storageRepo,
           required this.dataRepo})
           : super(ProfileState(user: user, isCurrentUser: isCurrentUser)) {
          // storageRepo
         //     .getUrlForFile(user!.avatarKey)
         //     .then((url) => add(ProvideImagePath(avatarPath: url)));

        ImageUrlCache.instance
           .getUrl(user!.avatarKey)
          .then((url) => add(ProvideImagePath(avatarPath: url)));
                     }

         @override
            Stream<ProfileState> mapEventToState(ProfileEvent event) async* {
           if (event is ChangeAvatarRequest) {
         yield state.copyWith(isImageSourceActionSheetVisible: true);
         } else if (event is OpenImagePicker) {
          yield state.copyWith(isImageSourceActionSheetVisible: false);
          try {
          final selectedImage =
        await _picker.pickImage(source: event.imageSource);
           if (selectedImage == null) return;
           final imageKey = await storageRepo.uploadFile(File(selectedImage.path));
          final user = state.user!.copyWith(avatarKey: imageKey);

            String? imageUrl;
          await Future.wait<void>([
            dataRepo.updateUser(user),
            storageRepo.getUrlForFile(imageKey).then((value) => imageUrl = value)
    ]);

    yield state.copyWith(avatarPath: imageUrl);
  } catch (e) {
    throw e;
  }
} else if (event is ProvideImagePath) {
  if (event.avatarPath != null)
    yield state.copyWith(avatarPath: event.avatarPath);
} else if (event is ProfileCityChanged) {
  yield state.copyWith(userCity: event.city);
} else if (event is ProfileNameChanged) {
  yield state.copyWith(userName1: event.name);
} else if (event is ProfileDescriptionChanged) {
  yield state.copyWith(userDescription: event.description);
} else if (event is SaveProfileChanges) {
  // handle save changes
  yield state.copyWith(formStatus: FormSubmitting());
  final updatedUser =
      state.user!.copyWith(city: state.userCity, name: state.userName1);
  try {
    // User userNew= updatedUser.copyWith(city: state.userCity,name: state.userName1,description: state.userDescription);
    await dataRepo.updateUser(updatedUser);
    print(updatedUser);
    yield state.copyWith(formStatus: SubmissionSuccess());
  } on Exception catch (e) {
    yield state.copyWith(formStatus: SubmissionFailed(e));
  } catch (e) {
    print(e);
  }
}
    }
      }

image

thank you for your patience :)

marcossevilla commented 3 years ago

@Oliwiana I think you should leave the form submission functionality on another bloc as I said previously with LoginBloc. Then you can listen for a LoginCompleted state (feel free to rename) with a BlocListener and then add an event to pass the user to the ProfileBloc.

If you can share a minimal reproducible sample on a repo I can create a PR to help, but I don't see any of your UI components so I can't give you appropriate feedback. No need to add Amplify as a dependency for the example, just create a Future.delayed to simulate the Amplify part or any dependency other than bloc for that matter. πŸ‘

Oliwiana commented 3 years ago

@marcossevilla thanks for the answer!

I shared a sample, it's here: https://github.com/Oliwiana/profile_bloc

I had a problem because each time I overwritten the data after switching to the next screen and as a result the first screen did not save. It is certainly a mistake in my thinking. Of course, every little hint will be a step forward for me. :)

marcossevilla commented 3 years ago

@Oliwiana I just left you a PR with the changes, hope that helps and if it doesn't I noticed you forgot to pass the params on the route method of your LastScreen widget:

await Navigator.of(context).push<void>(LastScreen.route());

it should have:

await Navigator.of(context).push<void>(
    LastScreen.route(
        username: bloc.state.username, 
        city: bloc.state.city,
        description: bloc.state.userDescription,
    )
);

I highly suggest you check the sample I left here:

I have an example of filling a sign-up form and showing a profile page with the data of that form here. It's done with flow_builder and formz but the logic is similar to what you want to achieve.

That can help you a lot, you just need to add the authentication with Amplify to your SignUpBloc when the form fields are filled.

Let me know if you need additional help. πŸ‘

Oliwiana commented 3 years ago

Thank you very much for your help @marcossevilla , not long ago I started to learn the architecture of bloc, and in many articles it was stated that there is one block per class. Perhaps I put the question wrong, that's why I drew it instead of writing. If you could take a look? image

big thanks for your time!

marcossevilla commented 3 years ago

@Oliwiana you can have a bloc for the general profile create feature and handle each page logic with a cubit, as it is simpler to use for a single page logic.

Your flow is good, you only need to create the cubits for each page and use BlocListener to add events to your ProfileBloc and when all its fields are filled, navigate to the last screen.

Would look something like this:

Screen Shot 2021-10-19 at 8 52 52 AM

Have you checked the example I shared with you before? It does exactly the same.

Let me know if that solves it. πŸ‘

marcossevilla commented 3 years ago

@Oliwiana did you manage to solve the issue?

Gene-Dana commented 3 years ago

Hey there πŸ‘‹πŸΌ Just cleaning up the issues list right now and that means I'm planning on closing this one here ! @Oliwiana, you are in good hands here. Feel free to continue to use this issue as a place to ask questions, and/or join our discord https://discord.gg/KzqFgDJw