Как обновить значение TextField с помощью StreamBuilder?

У меня есть textfield, и я использую sqflite базу данных в своем приложении. sqflite имеет значение, которое мне нужно присвоить моему textfield

Вот мой textfield код

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

Теперь в методе initstate моего класса я получаю значение из базы данных. Это асинхронная операция, поэтому на нее нужно время.

В моем классе блока есть следующий код

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

поэтому, как только я получаю значение из базы данных, я вызываю следующие

doctorNameChanged("valuefromdatabase");

но я не вижу значения в своем текстовом поле. Также в моей базе данных есть значение. Можно ли обновить значение без использования TextEditingController или setState. Я пытаюсь избежать этого, поскольку мой класс разделен на множество фрагментов и слишком сложен, чтобы использовать что-либо из вышеперечисленного. Я пробовал использовать тот же подход с RadioButton и CheckBox, и они, похоже, обновляются правильно. Значение также обновляется в _doctorName.stream.value, который присутствует в базе данных, но textfield не показывает никаких данных. Также я попытался изменить цвет textfield, поэтому проблем нет, и я могу видеть, что я набираю.

Я сделал небольшую демонстрацию приложения https://github.com/PritishSawant/demo/tree/master/lib

Вместо sqflite я использую shared preferences, но проблема не устранена


person Pritish    schedule 02.01.2020    source источник
comment
Попробуйте удалить initialValue: PatientHealthFormBloc.doctorNameValue и запустить его в initialData из StreamBuilder   -  person Stel    schedule 02.01.2020
comment
@Stel Спасибо, но не работает   -  person Pritish    schedule 02.01.2020
comment
Вы можете использовать TextEditingController (текст:)   -  person    schedule 04.01.2020
comment
@ChinkySight Я использую Stream, чтобы избежать TextEditingController. Приложение сложное, и использовать TextEditingController невозможно.   -  person Pritish    schedule 04.01.2020
comment
В вашем примере вы вообще не используете снимок. Какие данные вы ожидаете извлечь из него для использования в текстовом поле TextFormField? Почему вы не можете использовать TextEditingController?   -  person João Soares    schedule 05.01.2020
comment
Если вы хотите предварительно заполнить TextFormField значением из базы данных, которое пользователь затем изменит и сохранит в базе данных, зачем вы используете Stream? Похоже, вы добавляете сложности там, где это не требуется.   -  person João Soares    schedule 05.01.2020
comment
Было бы полезно, если бы вы могли объяснить, чего пытаетесь достичь. Почему вы хотите изменить значение TextFormField в реальном времени, учитывая, что это виджет, который будет активно использоваться пользователем. Ожидаете ли вы изменить значение, пока пользователь печатает? Это немного сбивает с толку, поскольку обычно вы загружаете существующее значение в TextField, а затем позволяете пользователю редактировать его и сохранять.   -  person João Soares    schedule 05.01.2020
comment
@ JoãoSoares Когда пользователь впервые заходит в текстовое поле, он что-то вводит и нажимает «Сохранить», данные текстового поля сохраняются в sqflite. Теперь, когда он переходит к этому текстовому полю при следующем повторном открытии приложения, я хочу отобразить значение, которое он уже набрал ранее. Я не думаю, что вопрос непонятен?   -  person Pritish    schedule 05.01.2020
comment
Хорошо, тогда, если вы используете Streams только из-за Bloc, почему бы вам просто не прослушать Stream в своем initState и не использовать setState для заполнения initialValue?   -  person João Soares    schedule 05.01.2020


Ответы (3)


Хорошо, я наконец нашел решение своей проблемы.

Ниже приведен мой код, в приведенном ниже примере я использовал SharedPreferences вместо sqflite. То же самое можно сделать с 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);
              },
            );
           },
        ),
      ),
    );
  }
}
person Pritish    schedule 05.01.2020
comment
Это чрезвычайно продуманное решение. Я бы хотел, чтобы вы объяснили, чего именно вы хотите достичь изначально, вместо того, чтобы требовать, чтобы мы исправили ваше конкретное решение. Когда я прокомментировал ваш вопрос, вы, похоже, хотите в реальном времени обновлять TextField, который может активно использоваться пользователем. Это очень странный и, вероятно, плохой UX. - person João Soares; 05.01.2020
comment
@ JoãoSoares Вы можете опубликовать свое решение. Я более чем счастлив назначить награду и принять ваше решение, если оно лучше моего. Я только что опубликовал более простое решение, чтобы все могли понять, но на самом деле мое приложение требует синхронизации с sqflite, а также с firestore, поэтому оно может выглядеть спроектированным для вас - person Pritish; 05.01.2020
comment
Если вы можете объяснить, почему вы хотите передавать (обновлять) значение TextFieldForm в то же время, когда пользователь может его использовать, возможно, я смогу вам помочь. Синхронизация с SQFlite и Firestore не имеет никакого отношения к моему утверждению, что представленное вами решение является чрезмерно спроектированным. - person João Soares; 05.01.2020
comment
Я уже несколько раз читал этот вопрос и видел код, которым вы поделились на GitHub. В вашем объяснении говорится о том, что вы хотите делать и как вы хотите писать код. Это не объясняет, почему вы хотите, чтобы TextFormField заполнялся таким образом с помощью потоковой передачи данных или предпосылки для такого поведения. - person João Soares; 05.01.2020
comment
Основываясь на вашем объяснении, я понимаю, что вы хотите, чтобы TextFormField передавал текущее значение в вашей базе данных. Затем вы ожидаете, что, когда пользователь сохраняет форму и база данных обновляется, значение в вашем TextFormField обновляется из базы данных. Даже если он уже обновлен, потому что вы использовали только что ввели новое значение. - person João Soares; 05.01.2020
comment
Вы пробовали предложенное мной решение? - person João Soares; 08.01.2020
comment
@Nudge Замечательно, что этот код сработал для вас, но я только хотел сообщить вам, что заданный вами вопрос и найденное вами решение противоречат друг другу. в вопросе вы четко сказали, что не хотели использовать TextEditController, если это возможно. Когда @ João Soares и @Chinky Sight предложили его использовать. Вы сказали, что это невозможно использовать. Почему внезапное изменение требований. Даже если это законное изменение требований, почему бы просто не удалить вопрос. Поскольку решение, которое вы придумали, было простым и предлагалось вам снова и снова в комментариях. - person Harshvardhan Joshi; 11.01.2020
comment
Я предлагаю вам попробовать решение @ JoãoSoares и принять этот ответ, если он сработает. так как это довольно просто, и теперь можно использовать TextEditController. - person Harshvardhan Joshi; 11.01.2020
comment
К сожалению, похоже, что пользователь не захотел приписывать награду, несмотря на усилия нескольких пользователей помочь. Что прискорбно. - person João Soares; 11.01.2020

Попробуйте следующий подход:

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);
      },
    ...

Дай мне знать, если тебе еще понадобится помощь.

person Harshvardhan Joshi    schedule 02.01.2020
comment
вы получаете новые имена в потоке? - person Harshvardhan Joshi; 02.01.2020
comment
вы получаете такие же новые имена в builder: (context, snapshot)? - person Harshvardhan Joshi; 02.01.2020
comment
Когда я набираю текст, я получаю набранный текст. На начальном этапе, когда я получаю данные из базы данных, он печатает значение базы данных. StreamBuilder работает нормально, и значение обновляется, когда я набираю вручную, но не обновляется, когда оно извлекается из базы данных. - person Pritish; 02.01.2020
comment
Чтобы вы могли получать обновленные данные из базы данных, они вам понадобятся внутри streambuilder с изменениями из метода, которые возвращают результаты базы данных. - person Stel; 02.01.2020
comment
@Stel, но он работает для радиокнопки и флажка, почему бы не для текстового поля. нет необходимости в другом построителе потоков для базы данных, так как я уже добавил streambuilder для текстового поля и изменил его значение, когда я получаю данные из базы данных - person Pritish; 02.01.2020
comment
@Nudge убедитесь, что вы используете метод doctorNameChanged в том же экземпляре блока, который вы слушаете в первую очередь. Если это не проблема, вероятно, это что-то иное, чем просто обновление текстового поля. - person Harshvardhan Joshi; 02.01.2020
comment
@HarshvardhanJoshi Я использую тот же экземпляр блока - person Pritish; 02.01.2020
comment
в этом случае происходит что-то еще ... вы можете опубликовать еще код? где вы получаете данные из базы данных и отправляете их в блок? - person Harshvardhan Joshi; 02.01.2020
comment
@HarshvardhanJoshi в методе initstate - person Pritish; 03.01.2020
comment
@HarshvardhanJoshi Я добавил в вопрос исходный код ссылки на демонстрационное приложение, проверьте, есть ли у вас время - person Pritish; 04.01.2020
comment
Изменение значения свойства TextFormField initialValue не приведет к изменению текущего отображаемого значения. Он изменится только при полной перестройке. - person João Soares; 05.01.2020
comment
@ JoãoSoares Какое тогда решение ?? - person Pritish; 05.01.2020

В моих комментариях предлагалось примерно следующее:

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);
        },
      ],
    ),
  );
}

Я не хотел писать этот ответ, не понимая, почему вы не хотите использовать TextEditingController или setState. Но это должно достичь того, чего вы хотите при использовании шаблона Bloc.

person João Soares    schedule 05.01.2020
comment
Вначале у меня была эта мысль, но, поскольку процитированный вопрос и @Nudge настаивали на том, чтобы не использовать TextEditController, я соскреб эту идею. Замечательно, что только с помощью контроллера решение становится простым и маленьким для точной работы. Отличная работа. - person Harshvardhan Joshi; 11.01.2020
comment
Спасибо, я ценю это. К сожалению, пользователь казался непреклонным в том, чтобы придерживаться своей идеи и не пытаться написать правильное решение, поэтому они решили не приписывать правильный ответ или вознаграждение. - person João Soares; 11.01.2020