How to implement a Slider within an AlertDialog in Flutter?

Issue

I am learning app development on Flutter and can’t get my Slider to work within the AlertDialog. It won’t change it’s value.
I did search the problem and came across this post on StackOverFlow:
Flutter – Why slider doesn’t update in AlertDialog?

I read it and have kind of understood it. The accepted answer says that:

The problem is, dialogs are not built inside build method. They are on a different widget tree. So when the dialog creator updates, the dialog won’t.

However I am not able to understand how exactly does it have to be implemented as not enough background code is provided.

This is what my current implementation looks like:

double _fontSize = 1.0;

@override
Widget build(BuildContext context) {
  return Scaffold(
      appBar: AppBar(
        title: Text(qt.title),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.format_size),
            onPressed: () {
              getFontSize(context);
            },
          ),
        ],
      ),
      body: ListView.builder(
          padding: EdgeInsets.symmetric(vertical: 15.0),
          itemCount: 3,
          itemBuilder: (context, index) {
            if (index == 0) {
              return _getListTile(qt.scripture, qt.reading);
            } else if (index == 1) {
              return _getListTile('Reflection:', qt.reflection);
            } else {
              return _getListTile('Prayer:', qt.prayer);
            }
          })
  );
}

void getFontSize(BuildContext context) {
  showDialog(context: context,builder: (context){
    return AlertDialog(
      title: Text("Font Size"),
      content: Slider(
        value: _fontSize,
        min: 0,
        max: 100,
        divisions: 5,
        onChanged: (value){
          setState(() {
            _fontSize = value;
          });
        },
      ),
      actions: <Widget>[
        RaisedButton(
          child: Text("Done"),
          onPressed: (){},
        )
      ],
    );
  });
}


Widget parseLargeText(String text) {...}

Widget _getListTile(String title, String subtitle) {...}

I understand that I will need to make use of async and await and Future. But I am not able to understand how exactly. I’ve spent more than an hour on this problem and can’t any more. Please forgive me if this question is stupid and noobish. But trust me, I tried my best.

Solution

Here is a minimal runnable example. Key points:

  • The dialog is a stateful widget that stores the current value in its State. This is important because dialogs are technically separate “pages” on your app, inserted higher up in the hierarchy
  • Navigator.pop(...) to close the dialog and return the result
  • Usage of async/await

import 'package:flutter/material.dart';

void main() => runApp(App());

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  double _fontSize = 20.0;

  void _showFontSizePickerDialog() async {
    // <-- note the async keyword here

    // this will contain the result from Navigator.pop(context, result)
    final selectedFontSize = await showDialog<double>(
      context: context,
      builder: (context) => FontSizePickerDialog(initialFontSize: _fontSize),
    );

    // execution of this code continues when the dialog was closed (popped)

    // note that the result can also be null, so check it
    // (back button or pressed outside of the dialog)
    if (selectedFontSize != null) {
      setState(() {
        _fontSize = selectedFontSize;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Text('Font Size: ${_fontSize}'),
            RaisedButton(
              onPressed: _showFontSizePickerDialog,
              child: Text('Select Font Size'),
            )
          ],
        ),
      ),
    );
  }
}

// move the dialog into it's own stateful widget.
// It's completely independent from your page
// this is good practice
class FontSizePickerDialog extends StatefulWidget {
  /// initial selection for the slider
  final double initialFontSize;

  const FontSizePickerDialog({Key key, this.initialFontSize}) : super(key: key);

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

class _FontSizePickerDialogState extends State<FontSizePickerDialog> {
  /// current selection of the slider
  double _fontSize;

  @override
  void initState() {
    super.initState();
    _fontSize = widget.initialFontSize;
  }

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('Font Size'),
      content: Container(
        child: Slider(
          value: _fontSize,
          min: 10,
          max: 100,
          divisions: 9,
          onChanged: (value) {
            setState(() {
              _fontSize = value;
            });
          },
        ),
      ),
      actions: <Widget>[
        FlatButton(
          onPressed: () {
            // Use the second argument of Navigator.pop(...) to pass
            // back a result to the page that opened the dialog
            Navigator.pop(context, _fontSize);
          },
          child: Text('DONE'),
        )
      ],
    );
  }
}

Answered By – boformer

Answer Checked By – Katrina (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *