Flutter — Как вызвать родительскую функцию виджета из дочернего виджета, сохраняя при этом состояние с переменной?

#flutter #dart #parameter-passing #flutter-provider

Вопрос:

У меня есть следующая структура кода: Родитель —gt; Ребенок —gt;gt; Внук. Мне нужно вызвать функцию в родительском от внука. Кроме того, логическая переменная должна быть доступна как у родителя, так и у внука для изменений пользовательского интерфейса (чтобы они были синхронизированы). Как мне это сделать?

Рабочий стол: (Родительский виджет)

 import 'dart:async'; import 'dart:io'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_sound/flutter_sound.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:voice_recorder/recording_info_provider.dart';  import 'central_icons_widget.dart'; import 'digital_display.dart'; import 'record_button_with_outer_circle_and_shadow.dart'; import 'recording_state.dart'; import 'recordings_list.dart';    /// const int tSampleRate = 44000; typedef _Fn = void Function();  /// Example app. class HomeScreen extends StatefulWidget {  const HomeScreen({Key? key}) : super(key: key);   @override  _HomeScreenState createState() =gt; _HomeScreenState(); }  class _HomeScreenState extends Statelt;HomeScreengt; {  FlutterSoundPlayer? _mPlayer = FlutterSoundPlayer();  FlutterSoundRecorder? _mRecorder = FlutterSoundRecorder();  bool _mPlayerIsInited = false;  bool _mRecorderIsInited = false;  bool _mplaybackReady = false;  String? _mPath;  StreamSubscription? _mRecordingDataSubscription;   Futurelt;voidgt; _openRecorder() async {  var statusMicPermission = await Permission.microphone.request();  if (statusMicPermission != PermissionStatus.granted) {  throw RecordingPermissionException('Microphone permission not granted');  }   var statusStoragePermission =  await Permission.manageExternalStorage.request();  if (statusStoragePermission != PermissionStatus.granted) {  throw Exception('Storage permission not granted');  }  await _mRecorder!.openAudioSession();  setState(() {  _mRecorderIsInited = true;  });  }   @override  void initState() {  super.initState();  // Be careful : openAudioSession return a Future.  // Do not access your FlutterSoundPlayer or FlutterSoundRecorder before the completion of the Future  _mPlayer!.openAudioSession().then((value) {  setState(() {  _mPlayerIsInited = true;  });  });  _openRecorder();  }   @override  void dispose() {  stopPlayer();  _mPlayer!.closeAudioSession();  _mPlayer = null;   stopRecorder();  _mRecorder!.closeAudioSession();  _mRecorder = null;  super.dispose();  }   Futurelt;Directorygt; _createFolder() async {  final folderName = "voice_recorder";  final dir = Directory("storage/emulated/0/$folderName");  if (await dir.exists()) {  // TODO:  print("exist");  } else {  // TODO:  print("not exist");  dir.create();  }  return dir;  }   Futurelt;IOSinkgt; createFile() async {  // var tempDir = await getExternalStorageDirectory();  // _mPath = '${tempDir!.path}/flutter_sound_example.pcm';   var directory = await _createFolder();  _mPath = '${directory.path}/flutter_sound_example.pcm';   var outputFile = File(_mPath!);  if (outputFile.existsSync()) {  await outputFile.delete();  }  return outputFile.openWrite();  }    Futurelt;voidgt; record() async {  assert(_mRecorderIsInited amp;amp; _mPlayer!.isStopped);  var sink = await createFile();  var recordingDataController = StreamControllerlt;Foodgt;();  _mRecordingDataSubscription =  recordingDataController.stream.listen((buffer) {  if (buffer is FoodData) {  sink.add(buffer.data!);  }  });  await _mRecorder!.startRecorder(  toStream: recordingDataController.sink,  codec: Codec.pcm16,  numChannels: 1,  sampleRate: tSampleRate,  );  //isRecording = _mRecorder!.isRecording;   setState(() {});  }    Futurelt;voidgt; stopRecorder() async {  await _mRecorder!.stopRecorder();  if (_mRecordingDataSubscription != null) {  await _mRecordingDataSubscription!.cancel();  _mRecordingDataSubscription = null;  }  _mplaybackReady = true;  }   _Fn? getRecorderFn() {  // RecordingState.isRecording = !RecordingState.isRecording;  // setState(() {});  // RecordingInfoProvider recordingInfoProvider =  // Provider.oflt;RecordingInfoProvidergt;(context, listen: false);   // recordingInfoProvider.updateRecordingStatus();   if (!_mRecorderIsInited || !_mPlayer!.isStopped) {  return null;  }  return _mRecorder!.isStopped  ? () {  record;  }  : () {  //stopRecorder().then((value) =gt; setState(() {}));  stopRecorder;  };  }   Function? toggleRecording() {  if (!_mRecorderIsInited || !_mPlayer!.isStopped) {  return null;  }  return _mRecorder!.isStopped  ? () {  record;  }  : () {  //stopRecorder().then((value) =gt; setState(() {}));  stopRecorder;  };  }   void play() async {  assert(_mPlayerIsInited amp;amp;  _mplaybackReady amp;amp;  _mRecorder!.isStopped amp;amp;  _mPlayer!.isStopped);  await _mPlayer!.startPlayer(  fromURI: _mPath,  sampleRate: tSampleRate,  codec: Codec.pcm16,  numChannels: 1,  whenFinished: () {  setState(() {});  }); // The readability of Dart is very special :-(  setState(() {});  }   Futurelt;voidgt; stopPlayer() async {  await _mPlayer!.stopPlayer();  }   _Fn? getPlaybackFn() {  if (!_mPlayerIsInited || !_mplaybackReady || !_mRecorder!.isStopped) {  return null;  }  return _mPlayer!.isStopped  ? play  : () {  stopPlayer().then((value) =gt; setState(() {}));  };  }     @override  Widget build(BuildContext context) {  RecordingInfoProvider recordingInfoProvider =  Provider.oflt;RecordingInfoProvidergt;(context, listen: false);   bool _isRecording = recordingInfoProvider.getRecordingStatus();   return Scaffold(  backgroundColor: Colors.white,  body: SafeArea(  child:  //FROSTED GLASS BACKGROUND  Container(  decoration: BoxDecoration(  borderRadius: const BorderRadius.all(  Radius.circular(25),  ),  gradient: LinearGradient(  colors: [  Colors.grey.withOpacity(0.3),  Colors.grey.withOpacity(0.1),  ],  begin: Alignment.topLeft,  end: Alignment.bottomRight,  ),  ),  //child: Expanded(  child: BackdropFilter(  filter: ImageFilter.blur(  sigmaX: 5,  sigmaY: 5,  ),  child: Column(  children: [  //TIMER (Digital Display)  DigitalDisplay(), //isRecording SHOULD UPDATE THIS WIDGET   //RECORD BUTTON //isRecording SHOULD UPDATE THIS WIDGET  RecordButtonWithOuterCircleAndShadow(  //outerCircleSize: outerCircleSize,  //isRecording: _isRecording, /  toggleRecording: () {},  ),   //MUSIC AND SETTINGS  CentralIconsWidget(),   //RECORDINGS LIST  RecordingsList(),  ],  ),  ),  //),  ),  ),  );  } }  //typedef VoidCallback = void Function();  

Кнопка записи без окружности и тени:

 import 'package:blobs/blobs.dart';  import 'package:flutter/material.dart';  import 'package:provider/provider.dart';  import 'package:voice_recorder/recording_state.dart';   import 'constants.dart';  import 'home_screen.dart';  import 'record_button_with_shadow.dart';  import 'recording_info_provider.dart';   class RecordButtonWithOuterCircleAndShadow extends StatefulWidget {  RecordButtonWithOuterCircleAndShadow({  Key? key,  required this.toggleRecording,  //required this.isRecording,  }) : //_isRecording = isRecording,  super(key: key);   final Function? toggleRecording;  //final VoidCallback? toggleRecording;  //final bool isRecording;  @override  Statelt;RecordButtonWithOuterCircleAndShadowgt; createState() =gt;  _RecordButtonWithOuterCircleAndShadowState();  }   class _RecordButtonWithOuterCircleAndShadowState  extends Statelt;RecordButtonWithOuterCircleAndShadowgt; {  bool _isRecording = false;   @override  Widget build(BuildContext context) {  RecordingInfoProvider recordingInfoProvider =  Provider.oflt;RecordingInfoProvidergt;(context, listen: false);  _isRecording = recordingInfoProvider.getRecordingStatus();   var outerCircleSize = 275.0;   return Container(  child:  //widget.isRecording  _isRecording  ? Stack(  children: [  Container(  height: outerCircleSize,  width: outerCircleSize,   //SHADOW AND BUTTON  child: Padding(  padding: EdgeInsets.all(40),  child: RecordButtonWithShadow(  toggleRecording: widget.toggleRecording,  //isRecording: _isRecording,  size: _isRecording ? 80 : 200,  ),  ),  ),  Blob.animatedRandom(  size: 325,  styles: BlobStyles(  color: kRecordButtonColor,  fillType: BlobFillType.stroke,  //minGrowth:6,  //gradient: LinearGradient(),  strokeWidth: 3,  ),  ),  ],  )  : Container(  height: outerCircleSize,  width: outerCircleSize,  decoration: BoxDecoration(  borderRadius: BorderRadius.circular(150),  border: Border.all(  width: _isRecording ? 5 : 3,  color: _isRecording ? kRecordButtonColor : Colors.grey,  style: BorderStyle.solid,  ),  ),   //SHADOW AND BUTTON  child: Padding(  padding: EdgeInsets.all(40),  child: RecordButtonWithShadow(  toggleRecording: widget.toggleRecording,  //isRecording: _isRecording,  size: _isRecording ? 80 : 200,  ),  ),  ),  );  }  }   //typedef VoidCallback = void Function(context);  

RecordButtonWithShadow:

 import 'dart:async';  import 'dart:math';   import 'package:flutter/material.dart';  import 'package:provider/provider.dart';   import 'constants.dart';  import 'package:animated_text_kit/animated_text_kit.dart';   import 'recording_info_provider.dart';  import 'recording_state.dart';   class RecordButtonWithShadow extends StatefulWidget {  RecordButtonWithShadow({  Key? key,  required this.toggleRecording,  //required this.isRecording,  required this.size,  }) : super(key: key);   //final bool isRecording;  final double size;  //final VoidCallback? toggleRecording;  final Function? toggleRecording;   @override  _RecordButtonWithShadowState createState() =gt; _RecordButtonWithShadowState();  }   class _RecordButtonWithShadowState extends Statelt;RecordButtonWithShadowgt;  with TickerProviderStateMixin {  late AnimationController animationController;  late Animationlt;doublegt; animation;  //String _buttonText = 'Record'.toUpperCase();  late bool _isRecording = false;   @override  void initState() {  animationController = AnimationController(  vsync: this,  duration: Duration(milliseconds: 500),  );  animation = Tweenlt;doublegt;(begin: 0, end: pi).animate(animationController);  super.initState();  }   @override  Widget build(BuildContext context) {  RecordingInfoProvider recordingInfoProvider =  Provider.oflt;RecordingInfoProvidergt;(context);  _isRecording = recordingInfoProvider.getRecordingStatus();  //_isRecording = RecordingState.isRecording;   var decorationRecording = BoxDecoration(  shape: BoxShape.rectangle,  borderRadius: BorderRadius.circular(widget.size * 0.35),  boxShadow: [  BoxShadow(  color: kRecordButtonShadowColor.withOpacity(0.25),  spreadRadius: widget.size * 0.1,  blurRadius: widget.size * 0.2,  offset: Offset(0, widget.size * 0.3),  ),  ],  );   var decorationNotRecording = BoxDecoration(  shape: BoxShape.circle,  boxShadow: [  BoxShadow(  color: kRecordButtonShadowColor.withOpacity(0.4),  spreadRadius: widget.size * 0.05,  blurRadius: widget.size * 0.15,  offset: Offset(0, widget.size * 0.09),  ),  ],  );   return Scaffold(  backgroundColor: Colors.transparent,  body: Center(  //SHADOW  child: Container(  decoration:  //widget.isRecording ? decorationRecording : decorationNotRecording,  _isRecording ? decorationRecording : decorationNotRecording,   //BUTTON  child: AnimatedContainer(  duration: Duration(milliseconds: 500),  decoration: BoxDecoration(shape: BoxShape.circle),  height: widget.size,  width: widget.size,  child: InkWell(  borderRadius:  //widget.isRecording  _isRecording  ? BorderRadius.circular(100)  : BorderRadius.circular(widget.size / 2),  onTap: () {  recordingInfoProvider.toggleRecordingStatus();  widget.toggleRecording;  },  child: AnimatedBuilder(  animation: animation,  builder: (context, child) {  return Container(  child: Transform.rotate(  angle: animation.value,  child: Stack(  alignment: Alignment.center,  children: [  Container(  alignment: Alignment.center,  decoration: BoxDecoration(  borderRadius:  //widget.isRecording  _isRecording  ? BorderRadius.circular(  widget.size * 0.3)  : null,  shape:  //widget.isRecording  _isRecording  ? BoxShape.rectangle  : BoxShape.circle,  gradient: _isRecording  ? LinearGradient(  begin: Alignment.topCenter,  end: Alignment.bottomCenter,  colors: [  kRecordButtonColor.withOpacity(0.8),  kRecordButtonColor,  kRecordButtonColor,  kRecordButtonColor.withOpacity(0.8),  ],  stops: const [  0,  0.3,  0.7,  1,  ],  )  : RadialGradient(  colors: [  kRecordButtonColor,  kRecordButtonColor,  kRecordButtonColor.withOpacity(0.2),  ],  stops: const [  0,  0.7,  1,  ],  ),  ),  ),  Container(  alignment: Alignment.center,  decoration: BoxDecoration(  borderRadius:   //widget.isRecording  _isRecording  ? BorderRadius.circular(  widget.size * 0.3,  )  : null,  shape: _isRecording  ? BoxShape.rectangle  : BoxShape.circle,  gradient:  //widget.isRecording  _isRecording  ? LinearGradient(  colors: [  Colors.white.withOpacity(0.2),  kRecordButtonColor  .withOpacity(0),  kRecordButtonColor  .withOpacity(0),  Colors.white.withOpacity(0.2),  ],  stops: const [  0,  0.3,  0.7,  1,  ],  )  : RadialGradient(  colors: [  kRecordButtonColor,  kRecordButtonColor,  kRecordButtonColor  .withOpacity(0.2),  ],  stops: const [  0,  0.7,  1,  ],  ),  ),  child:  //!widget.isRecording  !_isRecording  ? SizedBox(  width: 250.0,  child: Center(  child: DefaultTextStyle(  style: TextStyle(  fontSize: widget.size * 0.12,  letterSpacing: 3,  ),  child: AnimatedTextKit(  animatedTexts: [  TyperAnimatedText(  'Record'.toUpperCase(),  ),  ],  totalRepeatCount: 1,  onTap: () {  // startStopRecording();  // animateController();  },  ),  ),  ),  )  : null),  ],  ),  ),  );  }),  ),  ),  ),  ),  );  }  }  

Я могу передать функцию в качестве параметра, но как тогда обновить логическое значение?!. Кроме того, в настоящее время я получаю сообщение об ошибке: «java.lang.Исключение IllegalStateException: Ответ уже отправлен «.

Ответ №1:

Ваша идея передать функцию и логическое значение виджету внука верна. Что касается обновления логического значения, это должно быть сделано из родительского виджета.

Мне кажется, я заметил ошибку в вашем коде, но я не уверен, так как у меня нет полного кода в родительском виджете:

 toggleRecording: getRecorderFn(),  

Следует заменить на:

 toggleRecording: getRecorderFn,  

или

 toggleRecording: () =gt; getRecorderFn(),  

Способ, которым вы отправляете функцию, неверен, так как вы отправляете не функцию, а ее возврат, иначе говоря, когда виджет будет построен, ваш код вызовет getRecorderFn() и вернет его результат. Опять же, я не уверен, что возвращаемое значение getRecoderFn() этого может быть намеренным, но у меня нет остальной части кода, чтобы судить.

Что касается ошибки, требуется более подробная информация.

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

1. «Это должно быть сделано из родительского виджета» — как? Как мне поддерживать его в глобальном масштабе для состояния приложения?

2. Если вам нужно поддерживать состояние для всего приложения, я предлагаю вам использовать подход к управлению состоянием, или вы можете поднять состояние до более высокого родительского уровня, но это не рекомендуется, так как это приведет к большому количеству шаблонного кода и не будет эффективным.

3. Мне просто нужно логическое значение для синхронизации нескольких виджетов…

4. Вы можете использовать метод, который присутствует в ответе выше

5. Я уже использую его, это не помогает. Метод не обновляет логическое значение и состояние. Я обновил код.