简体   繁体   中英

How to sync stream variable in BlocState with the TextFormField

I am not able to figure out the right architecture of getting this done using firestore streams and flutter_bloc .

My goal:

I have a logged in user and I want to store the user details in the collection with the document id same as logged in user id and want to listen to changes, Throughout the program.

Meanwhile i would want to even store user details (if doesn't exist) and update user details.

My approach:

I am having a usermodel in UserState and i am listening to userModel via state using BlocBuilder.

Problem:

As i am using BlocBuilder my setState isn't working and TextFormField isn't working as it says beginBatchEdit on inactive InputConnection

Code:

UserCubit.dart

class UserCubit extends Cubit<UserState> {
  final _firestore = FirebaseFirestore.instance;
  final User? _currentUser = FirebaseAuth.instance.currentUser;
  UserCubit() : super(UserInitialState()) {
    emit(UserMainLoadingState());
    _firestore                        --> listening  to stream and updating state
        .collection("sample")
        .doc(_currentUser?.uid) 
        .snapshots()
        .listen((event) {
      event.exists
          ? emit(UserExists(sample: SampleModel.fromjson(event.data()!, event.id)))
          : {emit(UserNotExists())};
    });
  }

  Future<void> addSampleUser({required SampleModel sample}) async {
    emit(UserSideLoadingState());
    _firestore
        .collection('sample')
        .doc(_currentUser?.uid)
        .set(sample.toJson())
        .then((value) => emit(UserSavedUpdatedState()));
  }
}

UserState.dart

abstract class UserState extends Equatable {
  final SampleModel? sampleModel;
  const UserState({this.sampleModel});

  @override
  List<Object?> get props => [sampleModel];
}

class UserExists extends UserState {
  const UserExists({required SampleModel sample}) : super(sampleModel: sample);
}

Widget.dart (Save/Update User Details)

class _MyWidgetState extends State<MyWidget> {
  // @override
  var fullNameKey;
  TextEditingController? fullNameController;
  bool _formChecked = false;
  TextEditingController? phoneNumberController;
  Avatar? selectedAvatar;

  @override
  void initState() {
    fullNameController = TextEditingController();
    fullNameKey = GlobalKey<FormState>();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: BlocConsumer<UserCubit, UserState>(listener: (context, state) {
        if (state is UserSavedUpdatedState) {
          print("Saved/Updated User");
          context.goNamed(Routes.profileMain);
        }
      }, builder: (context, state) {
       // Here i am trying to get details from the state 
        selectedAvatar = state.sampleModel?.avatar == "boy"
            ? Avatar.boy
            : state.sampleModel?.avatar == "boy"
                ? Avatar.girl
                : null;
        fullNameController!.text = state.sampleModel?.name ?? "";
        if (state is UserMainLoadingState) {
          return const Center(child: CircularProgressIndicator());
        }
        return Scaffold(
            body: Column(
          children: [
            Row(
              children: [
                IconButton(
                    onPressed: () {
                      setState(() {
                        selectedAvatar = Avatar.girl;   -- > This doesn't work because of BlocBuilder
                      });
                    },
                    icon: const Icon(Icons.girl)),
                IconButton(
                    onPressed: () {
                      setState(() {
                        selectedAvatar = Avatar.boy; -- > This doesn't work because of BlocBuilder
                      });
                    },
                    icon: const Icon(Icons.boy))
              ],
            ),
            Form(
                key: fullNameKey,
                child: TextFormField(
                // BlocBuilder even freezes TextFormField
                  autovalidateMode: _formChecked
                      ? AutovalidateMode.always
                      : AutovalidateMode.disabled,
                  validator: (value) {
                    if (value == null ||
                        fullNameController?.text.trim() == "") {
                      return "Name cannot be empty";
                    }
                    if (value.length < 3) {
                      return "Username must be greater than 3 characters";
                    }
                    return null;
                  },
                  controller: fullNameController,
                  decoration: const InputDecoration(
                    labelText: "Full Name",
                  ),
                )).marginDown(),
            FilledButton(
              onPressed: () {
                setState(() {
                  _formChecked = true;
                });
                if (fullNameKey.currentState!.validate() &&
                    selectedAvatar != null) {
                  SampleModel sample = SampleModel(
                      name: fullNameController!.text,
                      avatar: selectedAvatar == Avatar.boy ? "boy" : "girl");
                  BlocProvider.of<UserCubit>(context)
                      .addSampleUser(sample: sample);
                }
              },
              child: const Text("Submit"),
            )
          ],
        ));
      }),
    );
  }
}

As soon as the submit button is clicked it erases the entire text and validator gets activated. Avatar selection doesn't work as well.

What is the best way to achieve the desired function using streams , flutter_bloc , Suggestions would be greatly appreciated

as far as I can see you pre-select the avatar based on the emitted state. However, I do not see that you return the selection via an event/function to the bloc/cubit. So this is needed in order to send the updated avatar with the next emit.

From what I can see, I would also possibly exchange the abstract class state with a class state implementing Equatable and the simply always copyWith the state for any updates. This way you always have the same UserState - no need for if and else if for state selection, however, the data of the state changes based on the situation. I think for a user bloc/cubit this makes the lifecycle a bit easier

UPDATE:

IconButton(
  onPressed: () {
    setState(() {
      context.read<UserCubit>.updateUser(selectedAvatar: Avatar.boy);
      selectedAvatar = Avatar.boy; -- > possibly no longer needed if returned from Cubit
    });
  },
  icon: const Icon(Icons.boy))

As for the state management, a state can look like this:

class TaskListState extends Equatable {
  const TaskListState({
    this.status = DataTransStatus.initial,
    this.taskList = const [],
    this.filter,
    this.error,
    this.editThisTaskId,
  });

  final DataTransStatus status;
  final List<TaskListViewmodel> taskList;
  final TaskListFilter? filter;
  final String? error;
  final String? editThisTaskId;

  TaskListState copyWith({
    DataTransStatus Function()? status,
    List<TaskListViewmodel> Function()? taskList,
    TaskListFilter Function()? filter,
    String Function()? error,
    String? Function()? editThisTaskId,
  }) {
    return TaskListState(
      status: status != null ? status() : this.status,
      taskList: taskList != null ? taskList() : this.taskList,
      filter: filter != null ? filter() : this.filter,
      error: error != null ? error() : this.error,
      editThisTaskId: editThisTaskId != null
          ? editThisTaskId() : this.editThisTaskId,
    );
  }

  @override
  List<Object?> get props => [
    status,
    taskList,
    filter,
    error,
    editThisTaskId,
  ];
}

which you use - in this case with a Stream - like this:

await emit.forEach<dynamic>(
      _propertiesRepository.streamTasks(event.propertyId),
      onData: (tasks) {
        return state.copyWith(
          status: () => DataTransStatus.success,
          taskList: () => List.from(
                  tasks.map((t) => TaskListViewmodel.fromDomain(t))),
        );
      },
      onError: (_, __) {
        return state.copyWith(
          status: () => DataTransStatus.failure,
        );
      },
    );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM