64

I'm new to flutter and need to create a gallery app that needs a custom dialog box to show the selected image. How can I implement that?

2

12 Answers 12

101

Use Dialog class which is a parent class to AlertDialog class in Flutter. Dialog widget has a argument , "shape" which you can use to shape the Edges of the Dialog box.

Here is a code sample:

 Dialog errorDialog = Dialog(
  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), //this right here
  child: Container(
    height: 300.0,
    width: 300.0,
   
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Padding(
          padding:  EdgeInsets.all(15.0),
          child: Text('Cool', style: TextStyle(color: Colors.red),),
        ),
        Padding(
          padding: EdgeInsets.all(15.0),
          child: Text('Awesome', style: TextStyle(color: Colors.red),),
        ),
        Padding(padding: EdgeInsets.only(top: 50.0)),
        TextButton(onPressed: () {
          Navigator.of(context).pop();
        },
            child: Text('Got It!', style: TextStyle(color: Colors.purple, fontSize: 18.0),))
      ],
    ),
  ),
);
showDialog(context: context, builder: (BuildContext context) => errorDialog);}
Sign up to request clarification or add additional context in comments.

1 Comment

assume that we have custom dialog class and we use it inside another Statefulwidget onTap: () { showDialog( context: context, builder: (context) { return CustomDialog(); }); }, do we have to pass the context to the customDialog or not. (my thought if the dialog do some change to that statefulwidget is this going to make the hole widget rebuild again )
72

Screenshot (Null Safe):

enter image description here


Code:

Just call this method:

void showCustomDialog(BuildContext context) {
  showGeneralDialog(
    context: context,
    barrierLabel: "Barrier",
    barrierDismissible: true,
    barrierColor: Colors.black.withOpacity(0.5),
    transitionDuration: Duration(milliseconds: 700),
    pageBuilder: (_, __, ___) {
      return Center(
        child: Container(
          height: 240,
          child: SizedBox.expand(child: FlutterLogo()),
          margin: EdgeInsets.symmetric(horizontal: 20),
          decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(40)),
        ),
      );
    },
    transitionBuilder: (_, anim, __, child) {
      Tween<Offset> tween;
      if (anim.status == AnimationStatus.reverse) {
        tween = Tween(begin: Offset(-1, 0), end: Offset.zero);
      } else {
        tween = Tween(begin: Offset(1, 0), end: Offset.zero);
      }
  
      return SlideTransition(
        position: tween.animate(anim),
        child: FadeTransition(
          opacity: anim,
          child: child,
        ),
      );
    },
  );
}

Comments

19

On a button click show dialog as -

showDialog(
        context: context,
        builder: (_) => LogoutOverlay(),
      );

Dialog design with two buttons -

class LogoutOverlay extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => LogoutOverlayState();
    }

    class LogoutOverlayState extends State<LogoutOverlay>
        with SingleTickerProviderStateMixin {
      AnimationController controller;
      Animation<double> scaleAnimation;

      @override
      void initState() {
        super.initState();

        controller =
            AnimationController(vsync: this, duration: Duration(milliseconds: 450));
        scaleAnimation =
            CurvedAnimation(parent: controller, curve: Curves.elasticInOut);

        controller.addListener(() {
          setState(() {});
        });

        controller.forward();
      }

      @override
      Widget build(BuildContext context) {
        return Center(
          child: Material(
            color: Colors.transparent,
            child: ScaleTransition(
              scale: scaleAnimation,
              child: Container(
                margin: EdgeInsets.all(20.0),
                  padding: EdgeInsets.all(15.0),
                  height: 180.0,

                  decoration: ShapeDecoration(
                      color: Color.fromRGBO(41, 167, 77, 10),
                      shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(15.0))),
                  child: Column(
                    children: <Widget>[
                      Expanded(
                          child: Padding(
                        padding: const EdgeInsets.only(
                            top: 30.0, left: 20.0, right: 20.0),
                        child: Text(
                          "Are you sure, you want to logout?",
                          style: TextStyle(color: Colors.white, fontSize: 16.0),
                        ),
                      )),
                      Expanded(
                          child: Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          Padding(
                            padding: const EdgeInsets.all(10.0),
                            child: ButtonTheme(
                                height: 35.0,
                                minWidth: 110.0,
                                child: RaisedButton(
                                  color: Colors.white,
                                  shape: RoundedRectangleBorder(
                                      borderRadius: BorderRadius.circular(5.0)),
                                  splashColor: Colors.white.withAlpha(40),
                                  child: Text(
                                    'Logout',
                                    textAlign: TextAlign.center,
                                    style: TextStyle(
                                        color: Colors.green,
                                        fontWeight: FontWeight.bold,
                                        fontSize: 13.0),
                                  ),
                                  onPressed: () {
                                    setState(() {
                                      Route route = MaterialPageRoute(
                                          builder: (context) => LoginScreen());
                                      Navigator.pushReplacement(context, route);
                                    });
                                  },
                                )),
                          ),
                          Padding(
                            padding: const EdgeInsets.only(
                                left: 20.0, right: 10.0, top: 10.0, bottom: 10.0),
                            child:  ButtonTheme(
                                height: 35.0,
                                minWidth: 110.0,
                                child: RaisedButton(
                                  color: Colors.white,
                                  shape: RoundedRectangleBorder(
                                      borderRadius: BorderRadius.circular(5.0)),
                                  splashColor: Colors.white.withAlpha(40),
                                  child: Text(
                                    'Cancel',
                                    textAlign: TextAlign.center,
                                    style: TextStyle(
                                        color: Colors.green,
                                        fontWeight: FontWeight.bold,
                                        fontSize: 13.0),
                                  ),
                                  onPressed: () {
                                    setState(() {
                                      /* Route route = MaterialPageRoute(
                                          builder: (context) => LoginScreen());
                                      Navigator.pushReplacement(context, route);
                                   */ });
                                  },
                                ))
                          ),
                        ],
                      ))
                    ],
                  )),
            ),
          ),
        );
      }
    }

2 Comments

I get the error "The method 'LoginScreen' isn't defined for the class LogoutOverlayState'. … Try correcting the name to the name of an existing method, or defining a method named 'LoginScreen'."
LoginScreen is a page name where it will redirect on 'Logout' click.
16

You just put this class in your project and call its method for showing dialog.
Using this class you don't need to write dialog code everywhere

class DialogUtils {
  static DialogUtils _instance = new DialogUtils.internal();

  DialogUtils.internal();

  factory DialogUtils() => _instance;

  static void showCustomDialog(BuildContext context,
      {@required String title, 
      String okBtnText = "Ok",
      String cancelBtnText = "Cancel",
      @required Function okBtnFunction}) {
    showDialog(
        context: context,
        builder: (_) {
          return AlertDialog(
            title: Text(title),
            content: /* Here add your custom widget  */,
            actions: <Widget>[
              FlatButton(
                child: Text(okBtnText),
                onPressed: okBtnFunction,
              ),
              FlatButton(
                  child: Text(cancelBtnText),
                  onPressed: () => Navigator.pop(context))
            ],
          );
        });
  }
 }

You can call this method like :

GestureDetector(
      onTap: () =>
              DialogUtils.showCustomDialog(context,
          title: "Gallary",
          okBtnText: "Save",
          cancelBtnText: "Cancel",
          okBtnFunction: () => /* call method in which you have write your logic and save process  */),
      child: Container(),
)

4 Comments

This solution works perfect. Can you elaborate on why using .internal? I would like to understand the background of the internal and factory semantics
just for create singleton class @JulianOtto
Why do you need to persist this instance anyway? That's the 100% stateless dialog and there is no need for saving it's the instance, you can go with a simple static method here
I tried this solution but I got this error Error: The argument type 'Function' can't be assigned to the parameter type 'void Function()?'.
14
  1. Alert Dialog
  2. Custom Dialog
  3. Full-Screen Dialog

ref: Flutter Alert Dialog to Custom Dialog | by Ishan Fernando | CodeChai | Medium

Alert Dialog

showDialog(
  context: context,
  builder: (BuildContext context) {
    return AlertDialog(
      title: Text("Alert Dialog"),
      content: Text("Dialog Content"),
      actions: [
        TextButton(
          child: Text("Close"),
          onPressed: () {
            Navigator.of(context).pop();
            },
        )
      ],
    );
  },
);

Custom Dialog

showDialog(
        context: context,
        builder: (BuildContext context) {
          return Dialog(
            shape: RoundedRectangleBorder(
                borderRadius:
                    BorderRadius.circular(20.0)), //this right here
            child: Container(
              height: 200,
              child: Padding(
                padding: const EdgeInsets.all(12.0),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    TextField(
                      decoration: InputDecoration(
                          border: InputBorder.none,
                          hintText: 'What do you want to remember?'),
                    ),
                    SizedBox(
                      width: 320.0,
                      child: RaisedButton(
                        onPressed: () {},
                        child: Text(
                          "Save",
                          style: TextStyle(color: Colors.white),
                        ),
                        color: const Color(0xFF1BC0C5),
                      ),
                    )
                  ],
                ),
              ),
            ),
          );
        });

Full-Screen Dialog

showGeneralDialog(
      context: context,
      barrierDismissible: true,
      barrierLabel: MaterialLocalizations.of(context)
          .modalBarrierDismissLabel,
      barrierColor: Colors.black45,
      transitionDuration: const Duration(milliseconds: 200),
      pageBuilder: (BuildContext buildContext,
          Animation animation,
          Animation secondaryAnimation) {
        return Center(
          child: Container(
            width: MediaQuery.of(context).size.width - 10,
            height: MediaQuery.of(context).size.height -  80,
            padding: EdgeInsets.all(20),
            color: Colors.white,
            child: Column(
              children: [
                RaisedButton(
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                  child: Text(
                    "Save",
                    style: TextStyle(color: Colors.white),
                  ),
                  color: const Color(0xFF1BC0C5),
                )
              ],
            ),
          ),
        );
      });

1 Comment

Hi @iHTCboy, I found a mistake not probably but due to updates some features get deprecated in your mentioned code for the Alert Dialog, so I am editing it, even you can also review it once.
7

I usually build a wrapper for the dialog that matches the app theme and avoids much redundant code.

PlaceholderDialog

class PlaceholderDialog extends StatelessWidget {
  const PlaceholderDialog({
    this.icon,
    this.title,
    this.message,
    this.actions = const [],
    Key? key,
  }) : super(key: key);

  final Widget? icon;
  final String? title;
  final String? message;
  final List<Widget> actions;

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(20.0),
      ),
      icon: icon,
      title: title == null
          ? null
          : Text(
              title!,
              textAlign: TextAlign.center,
            ),
      titleTextStyle: AppStyle.bodyBlack,
      content: message == null
          ? null
          : Text(
              message!,
              textAlign: TextAlign.center,
            ),
      contentTextStyle: AppStyle.textBlack,
      actionsAlignment: MainAxisAlignment.center,
      actionsOverflowButtonSpacing: 8.0,
      actions: actions,
    );
  }
}

Usage

    showDialog(
      context: context,
      builder: (ctx) => PlaceholderDialog(
        icon: Icon(
          Icons.add_circle,
          color: Colors.teal,
          size: 80.0,
        ),
        title: 'Save Failed',
        message: 'An error occurred when attempt to save the message',
        actions: [
          TextButton(
            onPressed: () => Navigator.of(ctx).pop(),
            child: Text('!Got It'),
          ),
        ],
      ),
    );

Result

enter image description here

Comments

4

An General E.g

showDialog(context: context,builder: (context) => _onTapImage(context)); // Call the Dialog.

_onTapImage(BuildContext context) {
    return Stack(
      alignment: Alignment.center,
      children: <Widget>[
        Image.network('https://via.placeholder.com/150',fit: BoxFit.contain,), // Show your Image
        Align(
          alignment: Alignment.topRight,
          child: RaisedButton.icon(
              color: Theme.of(context).accentColor,
              textColor: Colors.white,
              onPressed: () => Navigator.pop(context),
              icon: Icon(
                Icons.close,
                color: Colors.white,
              ),
              label: Text('Close')),
        ),
      ],
    );
  }

Comments

3

enter image description hereCustom Alert Dialog in Flutter

  void openAlert() {
    dialog = Dialog(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0)),
      //this right here
      child: Container(
        height: 350.0,
        width: double.infinity,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            ClipRRect(
              child: Image.asset(
                "assets/images/water1.jpg",
                width: double.infinity,
                height: 180,
                fit: BoxFit.cover,
              ),
              borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(16), topRight: Radius.circular(16)),
            ),
            Container(
              margin: EdgeInsets.only(top: 16),
              decoration: boxDecorationStylealert,
              width: 200,
              padding: EdgeInsets.symmetric(horizontal: 8),
              height: 50,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  GestureDetector(
                      onTap: () {
                        showToastMessage("-");
                      },
                      child:Image.asset("assets/images/subtraction.png",width: 30,height: 30,)),
                  Text(
                    "1",
                    style: TextStyle(
                        fontSize: 26,
                        fontWeight: FontWeight.bold,
                        color: black_color),
                  ),
                  GestureDetector(
                      onTap: () {
                        showToastMessage("+");
                      },
                      child:Image.asset("assets/images/add.png",width: 30,height: 30,)),
                ],
              ),
            ),
            Expanded(child: Container()),
            Row(
              children: [
                Expanded(
                  child: Padding(
                    padding: EdgeInsets.only(left: 12, right: 6),
                    child: MaterialButton(
                      onPressed: cancelClick,
                      color: green_color,
                      child: Text(
                        "CANCEL",
                        style: TextStyle(fontSize: 12, color: white_color),
                      ),
                      shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(4)),
                    ),
                  ),
                ),
                Expanded(
                  child: Padding(
                    padding: EdgeInsets.only(left: 6, right: 12),
                    child: MaterialButton(
                      onPressed: okClick,
                      color: green_color,
                      child: Text(
                        "OK",
                        style: TextStyle(fontSize: 12, color: white_color),
                      ),
                      shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(4)),
                    ),
                  ),
                )
              ],
            )
          ],
        ),
      ),
    );
    showDialog(
        context: context, builder: (BuildContext context) => dialog);
  }

Comments

2

You can now use AlertDialog and in content build your widget.

showDialog(
context: context,
builder: (BuildContext context) {
   return AlertDialog(
     shape: RoundedRectangleBorder(
     borderRadius: BorderRadius.all(Radius.circular(20.0))),
     backgroundColor: Colors.green,
     content: Container(
         height:200,
         width:200,
         decoration: BoxDecoration(
                    image: DecorationImage(
                          image: FileImage(filepath),
                          fit: BoxFit.cover))),}),

Comments

1

There's a working solution in my case:

  Future<void> _showMyDialog() async {
  return showDialog<void>(
  context: context,
  barrierDismissible: false, // user must tap button!
  builder: (BuildContext context) {
    return AlertDialog(
      title: Text('AlertDialog Title'),
      content: SingleChildScrollView(
        child: Column(
          children: <Widget>[
            Text('This is a demo alert dialog.'),
            Text('Would you like to confirm this message?'),
          ],
        ),
      ),
      actions: <Widget>[
        TextButton(
          child: Text('Confirm'),
          onPressed: () {
            print('Confirmed');
            Navigator.of(context).pop();
          },
        ),
        TextButton(
          child: Text('Cancel'),
          onPressed: () {
            Navigator.of(context).pop();
          },
        ),
      ],
    );
  },
);
}

Comments

1

Hope this is helpful: I've made static function in a separate class:

import 'package:flutter/material.dart';
import 'package:flutter_application_1/TGColors.dart';

class TGDialog 
{
  static doNothing() {  }  // stub needed for Function     parameters

/// Returns an AlertDialog with most optional parameters 

 static AlertDialog dlg(  BuildContext context,
                     { String      txtTitle      = 'WHAT? no title?'  ,
                       String      txtMsg        = 'WHAT? no content?',
                       String      txtBtn1       = 'CANCEL'           ,
                       String      txtBtn2       = 'OK'               , 
                       Function    funcBtn1      = doNothing          ,
                       Function    funcBtn2      = doNothing          ,
                       Color       colBackground = TGColors.Orange    ,
                       Color       colText       = TGColors.Indigo     } ) 
 {
  return
    AlertDialog(
        backgroundColor : colBackground,
        title           : Text(txtTitle),
        content         : Text(txtMsg),
        
        actions : <Widget>
        [
          TextButton(
            onPressed : () => { funcBtn1(), Navigator.pop(context,'Cancel')},
            child     : Text(txtBtn1, style: TextStyle(color: colText)),
          ),

          TextButton(
            onPressed :() => { funcBtn2(),Navigator.pop(context)  },
            child     : Text(txtBtn2, style: TextStyle(color: colText)),
          ),
        ],
    );
  }
} 

An example:

Positioned( bottom: 1, left: (screenW / 5.6), 
            child    : FloatingActionButton(
            heroTag  : 'clear',

            onPressed :() => showDialog<String>
            (
              context : context,
             
              builder : (BuildContext context) => 
                ///////////////////////////////////////////////////////
                TGDialog.dlg( context, 
                              txtTitle : 'Clear Order?',
                              txtMsg   : 'This resets all item counts' , 
                              funcBtn2 : resetOrder) 
                ///////////////////////////////////////////////////////
            ),
           
            child     : const Text('clear\nall', textAlign: TextAlign.center),
            shape     : RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(40),
                        ),
          ),
        ), 

etc. Btw: I like CSS webcolors so I defined them in a separate class like so:

import 'dart:ui';
/// Contains mainly web colors  (based on CSS)
/// 
/// Usage e.g:  ... = TGcolors.CornFlowerBlue
class TGColors
{
  static const  PrimaryColor =  Color(0xFF808080);
  static const  AliceBlue =  Color(0xFFF0F8FF);
  static const  AntiqueWhite = Color(0xFFFAEBD7);
  static const  Aqua = Color(0xFF00FFFF);
  static const  Aquamarine = Color(0xFF7FFFD4);  
  // etc.   

Comments

-2

This answer is more to the title of the questions than the details. I.e. "How to build a custom dialog in flutter?"

The main point is to create CustomDialog class that returns AlertDialog. This way code is cleaner and more readable. Also, CustomDialog extends ConsumerWidget, hence, providers can be used to get data, and no need to pass data as arguments to constructor:

custom_dialog.dart

 class CustomDialog extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final repeatSettings = ref.watch(repeatSettingsProvider);
    int time = repeatSettings.grammaryMinAutoLearningTimeMin!;
    var primaryColor = Theme.of(context).colorScheme.primary;
    var secondaryColor = Theme.of(context).colorScheme.secondary;
    var tertiaryColor = Theme.of(context).colorScheme.tertiary;
    return AlertDialog(
      //title: const Text('Засчитать урок?'),

      content: SizedBox(
        height: 100,   //!! applied to content area
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            Text(
              'Вы занимались меньше $time минут',
            ),
            SizedBox(
              height: 30, width: 300,
            ),
            Text('Засчитать урок?',
                style: TextStyle(color: primaryColor, fontSize: 18))
          ],
        ),
      ),
      actions: [
        XFlatButton(
          text: 'Да',
          width: 50,
          height: 25,
          onPressed: () {
            Navigator.of(context).pop();
          },
          bgColor: primaryColor,
        ),
        XFlatButton(
          text: 'Нет',
          width: 55,
          height: 25,
          onPressed: () {
            Navigator.of(context).pop();
          },
          bgColor: secondaryColor,
        ),
         XFlatButton(
          text: 'Отмена',
          width: 90,
          height: 25,
          onPressed: () {
            Navigator.of(context).pop();
          },
          bgColor: tertiaryColor,
        ),
        
      ],
    );
  }
}

This is what it looks like:

enter image description here

Note: SizedBox height applied to the content area and not the whole widget.

Call it:

onPressed: () async {
             
              return _dialogBuilder(context);
            },
...



Future<void> _dialogBuilder(BuildContext context) {
    return showDialog<void>(
      context: context,
      builder: (BuildContext context) {
        return CustomDialog();
      },
    );
  }

And if you like an XFlatButton (otherwise replace it with regular TextButton):

class XFlatButton extends StatelessWidget {
  final String text;
  final void Function()? onPressed;
  final double width;
  final double height;
  final IconData? iconData;
  final Color bgColor;
  final Color iconColor;
  final TextStyle textStyle;

  const XFlatButton(
      {required this.text,
      this.onPressed,
      this.width = 200,
      this.height = 40,
      super.key,
      this.iconData,
      this.bgColor = Colors.blue,
      this.iconColor = Colors.white,
      this.textStyle = const TextStyle(color: Colors.white)});
  @override
  Widget build(BuildContext context) {
    final flatButtonStyle = TextButton.styleFrom(
        padding: EdgeInsets.symmetric(horizontal: 10.0),
        shape: const RoundedRectangleBorder(
          borderRadius: BorderRadius.all(Radius.circular(20.0)),
        ),
        //backgroundColor: bgColor,
        backgroundColor: onPressed == null
            ? Theme.of(context).colorScheme.primaryContainer
            : bgColor);
    return SizedBox(
      width: width,
      height: height,
      child: TextButton(
          onPressed: onPressed,
          style: flatButtonStyle,
          //child: Text(text),
          child: iconData == null
              ? Text(
                  text,
                  style: textStyle,
                )
              : Row(mainAxisAlignment: MainAxisAlignment.center, children: [
                  Icon(
                    iconData,
                    color: iconColor,
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Text(
                    text,
                    style: textStyle,
                  ),
                ])),
    );
  }
}

2 Comments

Well i guess that bcs 1) you are shoving to someone who clames to be newbie an answer with provider 2) modal is not supposed to have an access to your providers - your showDIalog has a return type you know? You return users choice (most times a boolean result) and do whatever your logic needs to do next after async await of this result, this is how reusability in production app is done - one modal to use in many parts of the app with different logic
To 1) Newbie does not need to know about providers? What OP claims really does not matter: people come to question mostly by its title and this question has been viewed 150 K times. To 2) If you look into the code, you will see that I show the user information taken from the provider. Dialog is a widget like any other and can interact with providers. I don't see any subtle reason why not. Anyway, I appreciate your comment, despite I do not agree with your reasons to downvote.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.