Flutter grid of buttons that redirects to other page when clicked

4k Views Asked by At

Hi guys i am new to flutter. I have a grid of clickable buttons. Right now its only two, but it can grow to many. how can I refactor this to make it more dynamic and handle many future buttons? like a grid list of buttons that you can each click to navigate to different pages.

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Container(
        margin: EdgeInsets.all(10),
        padding: EdgeInsets.all(30.0),
        child: GridView.extent(
          maxCrossAxisExtent: 150,
          crossAxisSpacing: 15.0,
          mainAxisSpacing: 15.0,
          children: <Widget>[
            FlatButton(
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (_) => ProductScreen(),
                  ),
                );
              },
              padding: EdgeInsets.only(top: 33),
              child: Column(
                children: [
                  Icon(
                    Icons.shopping_cart_outlined,
                    color: Colors.white,
                  ),
                  Text(
                    "Products",
                    style: TextStyle(
                      color: Colors.white,
                    ),
                  ),
                ],
              ),
            ),
            FlatButton(
                 onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (_) => MailScreen(),
                  ),
                );
              },
              padding: EdgeInsets.only(top: 33),
              child: Column(
                children: [
                  Icon(
                    Icons.mail,
                    color: Colors.white,
                  ),
                  Text(
                    "Mail",
                    style: TextStyle(
                      color: Colors.white,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}
2

There are 2 best solutions below

1
On BEST ANSWER

Here is a solution:

1. Define a AppAction Model

class AppAction {
  final Color color;
  final String label;
  final Color labelColor;
  final IconData iconData;
  final Color iconColor;
  final void Function(BuildContext) callback;

  AppAction({
    this.color = Colors.blueGrey,
    this.label,
    this.labelColor = Colors.white,
    this.iconData,
    this.iconColor = Colors.white,
    this.callback,
  });
}

You could also have the route or its name instead of a callback function. Though, a callback will allow you to define other types of actions if needed. (example: launching an external URL, triggering a modal dialog, etc.)

2. Defining your Application Actions

final List<AppAction> actions = [
  AppAction(
    label: 'Products',
    iconData: Icons.shopping_cart_outlined,
    callback: (context) {
      Navigator.of(context)
          .push(MaterialPageRoute(builder: (_) => ProductScreen()));
    },
  ),
  AppAction(
    label: 'Mails',
    iconData: Icons.mail,
    callback: (context) {
      Navigator.of(context)
          .push(MaterialPageRoute(builder: (_) => MailScreen()));
    },
  ),
  AppAction(
    color: Colors.white,
    label: 'Urgent',
    labelColor: Colors.redAccent,
    iconData: Icons.dangerous,
    iconColor: Colors.redAccent,
    callback: (context) {
      Navigator.of(context)
          .push(MaterialPageRoute(builder: (_) => UrgentScreen()));
    },
  ),
  AppAction(
    color: Colors.green.shade200,
    label: 'News',
    labelColor: Colors.black,
    iconData: Icons.new_releases,
    iconColor: Colors.green,
    callback: (context) {
      Navigator.of(context)
          .push(MaterialPageRoute(builder: (_) => NewsScreen()));
    },
  ),
];

3. Define a generic ActionButton

class ActionButton extends StatelessWidget {
  final AppAction action;

  const ActionButton({
    Key key,
    this.action,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return OutlinedButton.icon(
      onPressed: () => action.callback?.call(context),
      style: OutlinedButton.styleFrom(
        backgroundColor: action.color,
        padding: const EdgeInsets.all(16.0),
      ),
      label: Text(action.label, style: TextStyle(color: action.labelColor)),
      icon: Icon(action.iconData, color: action.iconColor),
    );
  }
}

4. Simplify your HomePage

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return AppLayout(
      pageTitle: 'Home Page',
      child: Container(
        margin: EdgeInsets.all(10),
        padding: EdgeInsets.all(30.0),
        child: GridView.extent(
          maxCrossAxisExtent: 120,
          crossAxisSpacing: 15.0,
          mainAxisSpacing: 15.0,
          children: actions.map((action) => ActionButton(action: action)).toList(),
        ),
      ),
    );
  }
}

Voilà! If you want, here is a full standalone code sample to play with:

enter image description here

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      title: 'Flutter Demo',
      home: HomePage(),
    ),
  );
}

class AppAction {
  final Color color;
  final String label;
  final Color labelColor;
  final IconData iconData;
  final Color iconColor;
  final void Function(BuildContext) callback;

  AppAction({
    this.color = Colors.blueGrey,
    this.label,
    this.labelColor = Colors.white,
    this.iconData,
    this.iconColor = Colors.white,
    this.callback,
  });
}

final List<AppAction> actions = [
  AppAction(
    label: 'Products',
    iconData: Icons.shopping_cart_outlined,
    callback: (context) {
      Navigator.of(context)
          .push(MaterialPageRoute(builder: (_) => ProductScreen()));
    },
  ),
  AppAction(
    label: 'Mails',
    iconData: Icons.mail,
    callback: (context) {
      Navigator.of(context)
          .push(MaterialPageRoute(builder: (_) => MailScreen()));
    },
  ),
  AppAction(
    color: Colors.white,
    label: 'Urgent',
    labelColor: Colors.redAccent,
    iconData: Icons.dangerous,
    iconColor: Colors.redAccent,
    callback: (context) {
      Navigator.of(context)
          .push(MaterialPageRoute(builder: (_) => UrgentScreen()));
    },
  ),
  AppAction(
    color: Colors.green.shade200,
    label: 'News',
    labelColor: Colors.black,
    iconData: Icons.new_releases,
    iconColor: Colors.green,
    callback: (context) {
      Navigator.of(context)
          .push(MaterialPageRoute(builder: (_) => NewsScreen()));
    },
  ),
];

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return AppLayout(
      pageTitle: 'Home Page',
      child: Container(
        margin: EdgeInsets.all(10),
        padding: EdgeInsets.all(30.0),
        child: GridView.extent(
          maxCrossAxisExtent: 120,
          crossAxisSpacing: 15.0,
          mainAxisSpacing: 15.0,
          children:
              actions.map((action) => ActionButton(action: action)).toList(),
        ),
      ),
    );
  }
}

class AppLayout extends StatelessWidget {
  final String pageTitle;
  final Widget child;

  const AppLayout({Key key, this.pageTitle, this.child}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(pageTitle)),
      body: child,
    );
  }
}

class ActionButton extends StatelessWidget {
  final AppAction action;

  const ActionButton({
    Key key,
    this.action,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return OutlinedButton.icon(
      onPressed: () => action.callback?.call(context),
      style: OutlinedButton.styleFrom(
        backgroundColor: action.color,
        padding: const EdgeInsets.all(16.0),
      ),
      label: Text(action.label, style: TextStyle(color: action.labelColor)),
      icon: Icon(action.iconData, color: action.iconColor),
    );
  }
}

class ProductScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return AppLayout(
      pageTitle: ('Products Page'),
      child: Center(
        child: Text('LIST OF PRODUCTS'),
      ),
    );
  }
}

class MailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return AppLayout(
      pageTitle: 'Mail Page',
      child: Center(
        child: Text('LIST OF MAIL'),
      ),
    );
  }
}

class UrgentScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return AppLayout(
      pageTitle: 'Urgent Page',
      child: Center(
        child: Text('URGENT', style: TextStyle(color: Colors.redAccent)),
      ),
    );
  }
}

class NewsScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return AppLayout(
      pageTitle: 'News Page',
      child: Center(
        child: Text('NEWS', style: TextStyle(color: Colors.green)),
      ),
    );
  }
}
0
On

Create a separate widget for the button and pass the color, icon, Text and route in the params.

Instead of using push in navigator use pushNamed and used the passed route name here.