who-aditya-nawandar
who-aditya-nawandar

Reputation: 1344

Flutter - How do I call a Parent widget function from child widget while also maintaining the state with a variable?

I have the following code structure: Parent --> Child --> GrandChild. I need to call the function in parent from grandchild. Also, a boolean variable needs to be available in both parent and grandchild for UI changes (keeping them in sync). How do I do this?

HomeScreen: (Parent Widget)

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() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  FlutterSoundPlayer? _mPlayer = FlutterSoundPlayer();
  FlutterSoundRecorder? _mRecorder = FlutterSoundRecorder();
  bool _mPlayerIsInited = false;
  bool _mRecorderIsInited = false;
  bool _mplaybackReady = false;
  String? _mPath;
  StreamSubscription? _mRecordingDataSubscription;

  Future<void> _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();
  }

  Future<Directory> _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;
  }

  Future<IOSink> 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();
  }


  Future<void> record() async {
    assert(_mRecorderIsInited && _mPlayer!.isStopped);
    var sink = await createFile();
    var recordingDataController = StreamController<Food>();
    _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(() {});
  }


  Future<void> 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.of<RecordingInfoProvider>(context, listen: false);

    // recordingInfoProvider.updateRecordingStatus();

    if (!_mRecorderIsInited || !_mPlayer!.isStopped) {
      return null;
    }
    return _mRecorder!.isStopped
        ? () {
            record;
          }
        : () {
            //stopRecorder().then((value) => setState(() {}));
            stopRecorder;
          };
  }

  Function? toggleRecording() {
    if (!_mRecorderIsInited || !_mPlayer!.isStopped) {
      return null;
    }
    return _mRecorder!.isStopped
        ? () {
            record;
          }
        : () {
            //stopRecorder().then((value) => setState(() {}));
            stopRecorder;
          };
  }

  void play() async {
    assert(_mPlayerIsInited &&
        _mplaybackReady &&
        _mRecorder!.isStopped &&
        _mPlayer!.isStopped);
    await _mPlayer!.startPlayer(
        fromURI: _mPath,
        sampleRate: tSampleRate,
        codec: Codec.pcm16,
        numChannels: 1,
        whenFinished: () {
          setState(() {});
        }); // The readability of Dart is very special :-(
    setState(() {});
  }

  Future<void> stopPlayer() async {
    await _mPlayer!.stopPlayer();
  }

  _Fn? getPlaybackFn() {
    if (!_mPlayerIsInited || !_mplaybackReady || !_mRecorder!.isStopped) {
      return null;
    }
    return _mPlayer!.isStopped
        ? play
        : () {
            stopPlayer().then((value) => setState(() {}));
          };
  }



  @override
  Widget build(BuildContext context) {
    RecordingInfoProvider recordingInfoProvider =
        Provider.of<RecordingInfoProvider>(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();

RecordButtonWithOuterCircleAndShadow:

      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
    State<RecordButtonWithOuterCircleAndShadow> createState() =>
        _RecordButtonWithOuterCircleAndShadowState();
  }

  class _RecordButtonWithOuterCircleAndShadowState
      extends State<RecordButtonWithOuterCircleAndShadow> {
    bool _isRecording = false;

    @override
    Widget build(BuildContext context) {
      RecordingInfoProvider recordingInfoProvider =
          Provider.of<RecordingInfoProvider>(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() => _RecordButtonWithShadowState();
  }

  class _RecordButtonWithShadowState extends State<RecordButtonWithShadow>
      with TickerProviderStateMixin {
    late AnimationController animationController;
    late Animation<double> animation;
    //String _buttonText = 'Record'.toUpperCase();
    late bool _isRecording = false;

    @override
    void initState() {
      animationController = AnimationController(
        vsync: this,
        duration: Duration(milliseconds: 500),
      );
      animation = Tween<double>(begin: 0, end: pi).animate(animationController);
      super.initState();
    }

    @override
    Widget build(BuildContext context) {
      RecordingInfoProvider recordingInfoProvider =
          Provider.of<RecordingInfoProvider>(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),
                            ],
                          ),
                        ),
                      );
                    }),
              ),
            ),
          ),
        ),
      );
    }
  }

I can pass the function as a parameter but then how do I update the boolean value?!. Also, currently I am getting an error: "java.lang.IllegalStateException: Reply already submitted ".

Upvotes: 0

Views: 801

Answers (1)

Mohammad Kurjieh
Mohammad Kurjieh

Reputation: 1143

Your idea of passing the function and boolean to the grandchild widget is correct. Regarding updating the boolean, it must be done from the parent widget.

I think I spotted a bug in your code but I am not sure since I don't have the full code, in the parent widget:

toggleRecording: getRecorderFn(),

Should be replaced with:

toggleRecording: getRecorderFn,

or

toggleRecording: () => getRecorderFn(),

The way you are sending the function is wrong since you are not sending the function but rather its return, aka when the widget is built, your code will call getRecorderFn() and return its result. Again I am not sure about the return value of getRecoderFn() this might be intentional but I don't have the rest of the code to judge.

Regarding the error, more details are required.

Upvotes: 1

Related Questions