Флаттер: компонент с полным состоянием не обновляется внутри экрана / страницы с полным состоянием

#flutter #dart

#флаттер #дротик

Вопрос:

У меня есть один повторно используемый компонент ввода текста, который имеет состояние.Я использую этот компонент внутри экранной формы с полным состоянием, где я пытаюсь обновить свое поле ввода, но не работает.

Мой компонент TextInput.

 import 'package:flutter/material.dart';

class TextInputField extends StatefulWidget {
  final String label;
  final TextInputType keyboardType;
  final Function onInput;
  final Function validator;
  final value;
  final Function onTap;
  final Function onSaved;
  final readOnly;
  final bool showClearIcon;
  final Function onClearTap;
  final Function setValue;

  TextInputField(
      {@required this.label,
      this.onInput,
      this.validator,
      this.keyboardType = TextInputType.text,
      this.value,
      this.onTap,
      this.onSaved,
      this.readOnly = false,
      this.showClearIcon = false,
      this.onClearTap,
      this.setValue});

  @override
  _TextInputFieldState createState() => _TextInputFieldState();
}

class _TextInputFieldState extends State<TextInputField> {
  final TextEditingController controller = TextEditingController();
  String storedValue;

  @override
  void initState() {
    controller.text = widget.value;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    storedValue = widget.value.toString();

    return Padding(
      padding: const EdgeInsets.only(top: 15),
      child: TextFormField(
        key: Key(widget.label),

        readOnly: widget.readOnly,
        controller: controller,
        onChanged: widget.onInput,
        onTap: widget.onTap,
        onSaved: widget.onSaved,
        validator: widget.validator,
        // initialValue: widget.value ?? '',
        keyboardType: this.widget.keyboardType,
        textInputAction: this.widget.keyboardType == TextInputType.multiline
            ? TextInputAction.newline
            : null,
        maxLines: null,
        decoration: InputDecoration(
          border: OutlineInputBorder(
            borderRadius: const BorderRadius.all(
              const Radius.circular(10.0),
            ),
          ),
          filled: true,
          hintStyle: TextStyle(color: Colors.grey[800]),
          labelText: this.widget.label,
          fillColor: Colors.white70,
          alignLabelWithHint: true,
          isDense: true,
          suffixIcon: widget.showClearIcon
              ? InkWell(
                  onTap: widget.onClearTap,
                  child: Icon(Icons.close),
                )
              : null,
        ),
        textCapitalization: TextCapitalization.sentences,
      ),
    );
  }
}
  

Мой экран / форма

 import 'dart:async';

import 'package:edu_assist/widgets/buttons/form_buttons.dart';
import 'package:edu_assist/widgets/inputs/input_text_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

class AddBranch extends StatefulWidget {
  @override
  _AddBranchState createState() => _AddBranchState();
}

class _AddBranchState extends State<AddBranch> {
  final _formKey = GlobalKey<FormState>();

  String mapKey = DotEnv().env['MAP_KEY'];

  Completer<GoogleMapController> _controller = Completer();

  Position position;

  static final CameraPosition _kMumbaiLocation = CameraPosition(
    target: LatLng(19.04665, 74.8470429),
    zoom: 14.4746,
  );

  Future<void> _getMyLocation() async {
    Position p = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high);

    setState(() {
      position = p;
      _setNewMapLocation(p);
    });
  }

  Future<void> _setNewMapLocation(Position p) async {
    var newPostion = CameraPosition(
      target: LatLng(p.latitude, p.longitude),
      zoom: 19.151926040649414,
    );
    final GoogleMapController controller = await _controller.future;
    controller.animateCamera(CameraUpdate.newCameraPosition(newPostion));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Add New Branch'),
      ),
      body: SingleChildScrollView(
        child: Container(
          child: Form(
            key: _formKey,
            child: Padding(
              padding: const EdgeInsets.all(15),
              child: Column(
                children: [
                  TextInputField(
                    label: 'Branch Name*',
                    value: 'sds',
                    onSaved: (String val) {},
                  ),
                  TextInputField(
                    label: 'Lattitude',
                    value:
                        position == null ? null : position.latitude.toString(),
                    onSaved: (String val) {},
                  ),
                  TextInputField(
                    label: 'Longitude',
                    value:
                        position == null ? null : position.longitude.toString(),
                    onSaved: (String val) {},
                  ),
                  SizedBox(
                    height: 20,
                  ),
                  Container(
                    height: 150,
                    child: GoogleMap(
                      myLocationButtonEnabled: true,
                      myLocationEnabled: true,
                      initialCameraPosition: _kMumbaiLocation,
                      onMapCreated: (GoogleMapController controller) {
                        _controller.complete(controller);
                      },
                    ),
                  ),
                  
                  SizedBox(
                    height: 10,
                  ),
                  RaisedButton.icon(
                    onPressed: () {
                      _getMyLocation();
                    },
                    icon: Icon(Icons.location_on),
                    label: Text("Use My Current Location"),
                  ),
                  SizedBox(
                    height: 20,
                  ),
                  FormButtons(
                    onSuccessTap: () {},
                  )
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}
  

Здесь, когда я нажимаю кнопку «Использовать мое текущее местоположение» и хочу получить текущее местоположение пользователя и показать его в полях ввода широты и долготы.Я обновляю состояние, используя setState метод для установки Position , но состояние поля ввода не обновляется.
Я предполагаю, что это связано с тем, как я обрабатываю начальное значение в компоненте, вызывающем проблему.

Ответ №1:

При использовании setState виджеты с отслеживанием состояния внутри состояния будут вызывать метод сборки, а не initState метод. Ваша проблема в том, что вы устанавливаете значение контроллера в initState функции, по этой причине значение не обновляется. Решение вашей проблемы — передать TextEditingController в TextInputField .

 ///Check another time if you really want a stateful widget here
class TextInputField extends StatefulWidget {
  ...
  ...
  TextEditingController controller;
  ...
  ...

}
  

Затем вам нужно изменить значение position, которое вы устанавливаете для значения контроллера следующим образом

 Future<void> _getMyLocation() async {
    Position p = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high);

      //you don't need setState to update the textfield, controller will 
      // take care of that
      position = p;
      _postionTextController.text=p;
    

  }
  

Примечание: если вы создаете свой пользовательский виджет текстового поля, не создавайте контроллер внутри виджета, вместо этого передайте его виджету среди атрибутов, аналогичных способу TextField и TextFormField do

Комментарии:

1. Спасибо @Sami Kanafani. Ваше предложение сработало. Я ценю ваше мнение. В моем приложении есть множество форм с 10-20 полями ввода, поэтому я подумал о создании контроллера внутри компонента, который упрощает установку начального значения, иначе мне придется создавать множество контроллеров. Вы предлагаете какой-нибудь лучший способ избежать создания большого количества контроллеров?

2. Поскольку вы используете TextFormField , вы можете использовать атрибут onChange , таким образом, вам не нужно будет создавать контроллер для каждого текстового поля. В дополнение к этому и просто небольшое напоминание, не забудьте удалить контроллеры после переопределения метода dispose состояния