12

I have a textfield and i am using sqflite database in my app. The sqflite has a value which i need to assign to my textfield

Here is my textfield code

 StreamBuilder<String>(
    stream: patientHealthFormBloc.doctorName,
    builder: (context, snapshot) {
      return TextFormField(
        initialValue: patientHealthFormBloc.doctorNameValue,
        onChanged: (value) {
          patientHealthFormBloc.doctorNameChanged(value);
        },
        ...

Now in the initstate method of my class, i am fetching value from database. It being an asynchronous operation so it takes time.

My bloc class has a code like follows

Function(String) get doctorNameChanged => _doctorName.sink.add;

so as soon as i receive value from database i call following

doctorNameChanged("valuefromdatabase");

but i cant see the value in my textfield. Also there is a value present in my database. Is it possible to update the value without using TextEditingController or setState. I ma trying to avoid those as my class is divided in lot of chuncks and way too complicated to use any of the above I have tried using same approach with RadioButton and CheckBox and they seem to update properly. The value is also updated in _doctorName.stream.value which is present in the database but the textfield does not show any data. Also i tried changing color of textfield so there is no issue there as well as i am able to see what i type.

I have made a small demo of the app https://github.com/PritishSawant/demo/tree/master/lib

Instead of using sqflite, i am using shared preferences but the problem persists

J. S.
  • 8,905
  • 2
  • 34
  • 44
BraveEvidence
  • 53
  • 11
  • 45
  • 119
  • Try removing "initialValue: patientHealthFormBloc.doctorNameValue", and start it in the "initialData" from the StreamBuilder – Stel Jan 02 '20 at 11:16
  • @Stel Thanks but does not work – BraveEvidence Jan 02 '20 at 11:20
  • You can use the TextEditingController(text: ) –  Jan 04 '20 at 13:53
  • @ChinkySight I am using Stream to avoid TextEditingController. The app is complicated and using TextEditingController is not feasible – BraveEvidence Jan 04 '20 at 15:59
  • In your example you aren't using the snapshot at all. What data do you expect to extract from it to use in your TextFormField? Why can't you use a TextEditingController? – J. S. Jan 05 '20 at 12:25
  • If you want to preemptively fill a TextFormField with a value from a database which the user will then change and save that change to the database, why are you using a Stream? It seems like you are adding complexity where it isn't required. – J. S. Jan 05 '20 at 12:40
  • It would be helpful if you could explain what you are trying to achieve. Why you want to change a TextFormField's value in real time, taking into account that it's Widget that will be actively be used by the user. Are you expecting to change the value while the user is typing? It's a bit confusing to understand since typically you load the existing value into a TextField and then let the user edit that and save it. – J. S. Jan 05 '20 at 12:56
  • @JoãoSoares When user comes to the textfield for the first time, he types something and click save, the textfield data is saved in sqflite. Now when he comes to that textfield on the next app reopen, i want to display the value which he has already previously typed. I don't think the question is confusing to understand? – BraveEvidence Jan 05 '20 at 13:05
  • Ok, then if you are using Streams just because of Bloc, why don't you just listen to the Stream on your initState and use setState to fill in the initialValue? – J. S. Jan 05 '20 at 13:59

3 Answers3

4

OK so i finally found the solution to my problem.

Following is my code, I have just used SharedPreferences instead of sqflite for the below example.Same thing can be done with sqflite

class _MyHomePageState extends State<MyHomePage> {

  MyBloc myBloc = MyBloc();
  TextEditingController myController = TextEditingController();

  @override
  void dispose() {
    myBloc?.close();
    myController?.dispose();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    AppPreferences.setString("data", "this is my data");
    AppPreferences.getString("data").then((value){
      myBloc.dataChanged(value);
    });
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: StreamBuilder(
          stream: myBloc.data,
           builder: (context,snapshot){

            debugPrint(snapshot.data);
            myController.value = myController.value.copyWith(text: myBloc.dataValue);

            return TextFormField(
              controller: myController,
              onChanged: (value){
                myBloc.dataChanged(value);
              },
            );
           },
        ),
      ),
    );
  }
}
BraveEvidence
  • 53
  • 11
  • 45
  • 119
  • 2
    This an extremely over engineered solution. I wish you had explained what exactly you wanted to achieve originally instead of wanting us to fix your specific solution. As I commented on your question, you seem to be wanting to do real time updates to a TextField that may be being used actively by the user. This is very strange and likely bad UX. – J. S. Jan 05 '20 at 13:30
  • @JoãoSoares Can you post your solution. I am more than happy to award the bounty and accept your solution if its better than mine. I have just posted an easier solution so everyone can understand but in reality my app requires syncing with sqflite as well as firestore so it may look over engineered to you – BraveEvidence Jan 05 '20 at 13:34
  • If you can explain why you want to Stream (update) the value of a TextFieldForm at the same time the user might be using it, I may be able to help you. Syncing with SQFlite and Firestore has no involvement in my claim that the solution you presented is over-engineered. – J. S. Jan 05 '20 at 13:39
  • I have read the question multiple times already and have seen the code you shared on GitHub. Your explanation speaks of what you want to do and how you want the code written. It does not explain why you want to have a TextFormField being filled in that way with data Streaming or the premise for that behavior. – J. S. Jan 05 '20 at 13:48
  • Based on your explanation, my understanding is that you want a TextFormField to be streaming the current value on your database. Then you expect that when the user saves the form and the database is updated the value in your TextFormField get updated from the database. Even though it is already updated because your used just typed in the new value. – J. S. Jan 05 '20 at 13:53
  • Did you attempt my proposed solution? – J. S. Jan 08 '20 at 11:56
  • @Nudge It's great that this code worked out for you, but I only wanted to let you know that the question you asked and the solution you found are contradicting each other. in question you clearly said you didn;t wanted to use `TextEditController` if possible. When @João Soares and @Chinky Sight suggested using it. you said it's not feasible to use it. Why a sudden change in requirement. Even If it is a legit change in requirement, why not just remove the question. Since the solution you came up with was simple and was suggested to you over and over again in the comments. – Harshvardhan Joshi Jan 11 '20 at 16:48
  • I would suggest that you try out the solution by @JoãoSoares and Accept that answer if worked. since it's pretty simple and now it's okay to use the `TextEditController`. – Harshvardhan Joshi Jan 11 '20 at 16:54
  • Unfortunately it seems the user did not want to attribute the bounty despite the effort of several users to help. Which is unfortunate. – J. S. Jan 11 '20 at 17:05
3

Try the following approach:

StreamBuilder<String>(
  stream: patientHealthFormBloc.doctorName,
  builder: (context, snapshot) {
    String doctorName = patientHealthFormBloc.doctorNameValue;
    if(snapshot.hasData){
      doctorName = snapshot.data/*(your name string from the stream)*/;
    } 
    return TextFormField(
      initialValue: doctorName,
      onChanged: (value) {
        patientHealthFormBloc.doctorNameChanged(value);
      },
    ...
halfer
  • 19,824
  • 17
  • 99
  • 186
Harshvardhan Joshi
  • 2,855
  • 2
  • 18
  • 31
  • are you receiving new names in stream? – Harshvardhan Joshi Jan 02 '20 at 11:41
  • are you receiving the same new names in `builder: (context, snapshot)`? – Harshvardhan Joshi Jan 02 '20 at 11:43
  • When i type a text, i receive the typed text. At the initial stage when i fetching from db then it is printing the db value.The StreamBuilder is working fine and the value updates when i manually type but it does not update when it is fetched from database – BraveEvidence Jan 02 '20 at 11:46
  • For you to get refreshed data from database, you'll need it inside a streambuilder with changes from method that return the database results – Stel Jan 02 '20 at 11:57
  • @Stel but it works for radiobutton and checkbox, why not for textfield. there is not need for another stream builder for database as i have already added a streambuilder for textfield and changing it value when i get data from db – BraveEvidence Jan 02 '20 at 11:59
  • @Nudge make sure that you are using the `doctorNameChanged` method on the same bloc instance which you are listening from in the first place. If that's not a problem, this is probably something other than just update of text field. – Harshvardhan Joshi Jan 02 '20 at 12:12
  • @HarshvardhanJoshi I am using the same bloc instance – BraveEvidence Jan 02 '20 at 13:40
  • in that case something else is happening.. can you post more code ? where you are fetching data from db and sending to bloc? – Harshvardhan Joshi Jan 02 '20 at 13:44
  • @HarshvardhanJoshi in the initstate method – BraveEvidence Jan 03 '20 at 11:10
  • @HarshvardhanJoshi I have added a demo app link source code in the question, please check if you have time – BraveEvidence Jan 04 '20 at 10:36
  • Changing the value of a `TextFormField`'s `initialValue` property will not make it change the current value being displayed. It will only change on a full rebuild. – J. S. Jan 05 '20 at 12:43
  • @JoãoSoares What the solution then?? – BraveEvidence Jan 05 '20 at 13:02
1

What was suggesting in my comments was something like this:

TextEditingController _textEditingController = TextEditingController();

@override
void initState() {
  patientHealthFormBloc.doctorName.listen((snapshot){
    setState((){
      _textEditingController.text = snapshot;
    });
  });
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Column(
      children: <Widget>[
      TextFormField(
        controller: _textEditingController,
        onChanged: (value) {
          patientHealthFormBloc.doctorNameChanged(value);
        },
      ],
    ),
  );
}

I didn't want to write this answer without understanding why you didn't want to use a TextEditingController or a setState. But this should achieve what you want while using the Bloc pattern.

J. S.
  • 8,905
  • 2
  • 34
  • 44
  • I had this thought in the beginning, but since the question quoted and @Nudge was insisting on not using the `TextEditController`, so I scraped that Idea. It's great that only by using the controller the solution becomes simple and small to work accurately. Great Job. – Harshvardhan Joshi Jan 11 '20 at 16:56
  • 2
    Thank you, I appreciate it. Unfortunately the user seemed adamant on sticking to his own idea and not attempt to write a proper solution, as such they decided not to attribute the correct response or the bounty. – J. S. Jan 11 '20 at 17:33
  • When I used this approach I got this warning `Another exception was thrown: setState() or markNeedsBuild() called during build.` It is working but I am just bothered – NothingBox Sep 07 '22 at 13:44
  • It sould be related to whichever state management solution you are using. – J. S. Sep 07 '22 at 17:11