10

How do we add a TextField inside a StreamBuilder? I have a TextField / TextFormField as one of the widgets inside the builder function of either a StreamBuilder or FutureBuilder, whenever we try to interact with the textfield it just refreshes the entire builder widget and calls the stream/future again.

body: StreamBuilder(
      stream: getClientProfile().snapshots(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.active) {
          print(snapshot.data.data);
          Client tempClient = Client.from(snapshot.data);
          print('details = ${tempClient.representative.email} ${tempClient
              .address.location} ${tempClient.businessDescription}');
          return Container(
            child: Column(
              children: <Widget>[
                TextFormField(

                )
              ],
            ),
          );
        } else if (snapshot.connectionState == ConnectionState.waiting) {
          return Center(child: CircularProgressIndicator());
        } else {
          return Center(
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Icon(Icons.error),
                ),
                Text('Error loading data')
              ],
            ),
          );
        }
      }),

and firestore function

DocumentReference getClientProfile() {
   return _firestore.collection(SELLERS_COLLECTION).document(_uid);
}

What I want to achieve, is to have a form with pre-filled data from firestore document, basically an edit form. Is there any other way I could achieve the same or am I doing something wrong structurally ?

EDIT:

code after suggested edits.

    import 'package:flutter/material.dart';
import 'Utils/globalStore.dart';
import 'models/client_model.dart';
import 'dart:async';

class EditProfileInformation extends StatefulWidget {
  @override
  EditProfileInformationState createState() {
    return new EditProfileInformationState();
  }
}

class EditProfileInformationState extends State<EditProfileInformation> {
  Stream dbCall;
  final myController = TextEditingController();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    dbCall = getClientProfile().snapshots();
    myController.addListener(_printLatestValue);
  }

  _printLatestValue() {
    print("Second text field: ${myController.text}");
  }

  @override
  void dispose() {
    myController.removeListener(_printLatestValue);
    myController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
//      key: _scaffoldKey,
      appBar: AppBar(
        title: Text(
          'Edit profile',
          style: TextStyle(),
        ),
      ),

      body: StreamBuilder(
          stream: dbCall,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.active) {
              print(snapshot.data.data);
              Client tempClient = Client.from(snapshot.data);
              print('details = ${tempClient.representative.email} ${tempClient
                  .address.location} ${tempClient.businessDescription}');
              return Container(
                child: Column(
                  children: <Widget>[
                    Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: TextField(
                        controller: myController,
                      ),
                    )
                  ],
                ),
              );
            } else if (snapshot.connectionState == ConnectionState.waiting) {
              return Center(child: CircularProgressIndicator());
            } else {
              return Center(
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Icon(Icons.error),
                    ),
                    Text('Error loading data')
                  ],
                ),
              );
            }
          }),
      floatingActionButton: FloatingActionButton(
        onPressed: () {

        },
        child: Icon(Icons.done),
      ),
    );
  }
}
DarshanGowda0
  • 452
  • 3
  • 11

1 Answers1

9

In order to use a StreamBuilder correctly you must ensure that the stream you are using is cached on a State object. While StreamBuilder can correctly handle getting new events from a stream, receiving an entirely new Stream will force it to completely rebuild. In your case, getClientProfile().snapshots() will create an entirely new Stream when it is called, destroying all of the state of your text fields.

class Example extends StatefulWidget {
  @override
  State createState() => new ExampleState();
}

class ExampleState extends State<Example> {
  Stream<SomeType> _stream;

  @override
  void initState() {
    // Only create the stream once
    _stream = _firestore.collection(collection).document(id);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return new StreamBuilder(
      stream: _stream,
      builder: (context, snapshot) {
        ...

      },
    );
  }
}

EDIT: it sounds like there are other problems which I cannot diagnose from the code snippet you provided.

Jonah Williams
  • 20,499
  • 6
  • 65
  • 53
  • Yes, this makes sense. The widget doesnt refresh anymore when I put it this way. – DarshanGowda0 Jul 28 '18 at 19:45
  • But, the problem of the stream being called again persists. The interaction with the textfield still triggers the future or the stream call. Doesn't happen with any other widgets inside the builder(example: DropDownButton). Firestore document reads are charged :P hence the question. – DarshanGowda0 Jul 28 '18 at 19:48
  • 1
    Are you using a TextEditingController? – Jonah Williams Jul 28 '18 at 19:53
  • yes, with/with out TextEditingController still makes the call. – DarshanGowda0 Jul 28 '18 at 19:58
  • can you post the rest of the code showing how you use the controller? – Jonah Williams Jul 28 '18 at 20:15
  • Okay, I think this is an issue with respect to keyboard. So, I observed this, the stream call is triggered every time the keyboard appears/disappears from the screen. I verified it by disabling the virtual keyboard on the emulator(doesn't trigger now). – DarshanGowda0 Jul 28 '18 at 20:23
  • I can't reproduce the issue - see https://gist.github.com/jonahwilliams/bb280d523bebf01fcc5415bbf2610b0a. It works fine for me – Jonah Williams Jul 28 '18 at 21:42
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176953/discussion-between-darshangowda0-and-jonah-williams). – DarshanGowda0 Jul 28 '18 at 22:58
  • Perfect answer! – Monis May 02 '20 at 09:59