Dave
Dave

Reputation: 29121

Flutter snackbar alternative or easier method than wrapping everything in Scaffold?

I'm working on my first Flutter app (debugging on my Android phone). I have a list with row items. When you long-press the row, it copies the content into the user's clipboard. This is working great!

But I need to let the user know that the content was copied.

I've attempted to follow many tutorials on trying to get the row surrounded by a build method or inside a Scaffold, but I can't get any to work. Is there an alternative method to notifying the user (simply) that something like "Copied!" took place?

Notice the commented out Scaffold.of(... below. It just seems like there must be an easier method to notifying the user other than wrapping everything in a Scaffold. (and when I try, it breaks my layout).

import 'package:flutter/material.dart';
import 'package:my_app/Theme.dart' as MyTheme;
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/services.dart';

class RowRule extends StatelessWidget {
  final DocumentSnapshot ruleGroup;

  RowRule(this.ruleGroup);

  _buildChildren() {
    var builder = <Widget>[];
    if (!ruleGroup['label'].isEmpty) {
      builder.add(new Text(ruleGroup['label'],
          style: MyTheme.TextStyles.articleContentLabelTextStyle));
    }
    if (!ruleGroup['details'].isEmpty) {
      builder.add(new Text(ruleGroup['details'],
          style: MyTheme.TextStyles.articleContentTextStyle));
    }
    return builder;
  }


  @override
  Widget build(BuildContext context) {
    return new GestureDetector(
        onLongPress: () {
          Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " "  + ruleGroup['details']));
//          Scaffold.of(context).showSnackBar(SnackBar
//            (content: Text('text copied')));
        },
        child: Container(
          margin: const EdgeInsets.symmetric(vertical: 3.0),
          child: new FlatButton(
            color: Colors.white,
            padding: EdgeInsets.symmetric(horizontal: 0.0),
            child: new Stack(
              children: <Widget>[
                new Container(
                  margin: const EdgeInsets.symmetric(
                    vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
                  child: new Container(
                      child: Padding(
                        padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
                        child: new Column(
                          crossAxisAlignment: CrossAxisAlignment.stretch,
                          children: _buildChildren(),
                        ),
                    )),
              )
            ],
          ),
        ),
      ));
    }
  }

The goal is to have a page like this (see image), which I have, and it works and scrolls...etc, but I cannot get it to work with a Scaffold, and therefore, haven't been able to use the snackbar. Each "Row" (which this file is for) should show a snackbar on longPress.

enter image description here

Upvotes: 7

Views: 10156

Answers (6)

Bruce Brookshire
Bruce Brookshire

Reputation: 63

I made a dropdown banner package on pub that allows you to easily notify users of errors or confirmation of success. It's a work in progress as I continue to add visually rich features.

Upvotes: 1

Harsh Bhikadia
Harsh Bhikadia

Reputation: 10865

I read your comments on all answers and here is my conslusion:

You need ScaffoldState object that is just above the widget in tree to show Snackbar. You can either get it through GlobalKey as many have suggested. Fairly simple if the Scaffold is created inside build of the widget, but if it is outside the widget (in your case) then it becomes complicated. You need to pass that key, wherever you need it through Constructor arguments of child widgets.

Scaffold.of(context) is a very neat way to just do that. Just like an InheritedWidget, Scaffold.of(BuildContext context) gives you access of the closest ScaffoldState object above the tree. Else it could be a nightmare to get that instance (by passing it through as constructor arguments) if your tree was very deep.

Sorry, to disappoint but I don't think there is any better or cleaner method than this, if you want to get the ScaffoldState that is not built inside build of that widget. You can call it in any widget that has Scaffold as a parent.

Upvotes: 0

CopsOnRoad
CopsOnRoad

Reputation: 267474

You can use GlobalKey to make it work the way you want it.

enter image description here

Since I don't have access to your database stuff, this is how I gave you an idea to do it. Copy and paste this code in your class and make changes accordingly. I also believe there is something wrong in your RowRule class, can you just copy the full code I have given you and run?

void main() => runApp(MaterialApp(home: HomePage()));

class HomePage extends StatelessWidget {
  final GlobalKey<ScaffoldState> _key = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color(0xFFFFFFFF).withOpacity(0.9),
      key: _key,
      body: Column(
        children: <Widget>[
          Container(
            color: Color.fromRGBO(52, 56, 245, 1),
            height: 150,
            alignment: Alignment.center,
            child: Container(width: 56, padding: EdgeInsets.only(top: 12), decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.yellow)),
          ),
          Expanded(
            child: ListView.builder(
              padding: EdgeInsets.zero,
              itemCount: 120,
              itemBuilder: (context, index) {
                return Container(
                  color: Colors.white,
                  margin: const EdgeInsets.all(4),
                  child: ListTile(
                    title: Text("Row #$index"),
                    onLongPress: () => _key.currentState
                      ..removeCurrentSnackBar()
                      ..showSnackBar(SnackBar(content: Text("Copied \"Row #$index\""))),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

Upvotes: 8

Aman Malhotra
Aman Malhotra

Reputation: 3186

These is a simple plugin replacement for the Snackbar named "Flushbar". You can get the plugin here - https://pub.dartlang.org/packages/flushbar You don't have to take care of any wrapping of widgets into scaffold also you get a lot of modifications for you like background gradient, adding forms and so on into Snackbar's and all. Inside your onLongPressed in GestureDetectore you can do this.

onLongPressed:(){
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " "  + ruleGroup['details']));
Flushbar(
             message:  "Copied !!",
             duration:  Duration(seconds: 3),              
          )..show(context);
}

This will display the snackbar in you app where you would want to see it also you can get a lot of modification available to you so the you can make it look as per your app.

Upvotes: 4

CopsOnRoad
CopsOnRoad

Reputation: 267474

There are couple of things you need to do, like use onPressed property of the FlatButton it is mandatory to allow clicks, wrap your GestureDetector in a Scaffold. I have further modified the code so that it uses GlobalKey to make things easy for you.

Here is the final code (Your way)

class RowRule extends StatelessWidget {
  final GlobalKey<ScaffoldState> globalKey = GlobalKey();
  final DocumentSnapshot ruleGroup;

  RowRule(this.ruleGroup);

  _buildChildren() {
    var builder = <Widget>[];
    if (!ruleGroup['label'].isEmpty) {
      builder.add(new Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle));
    }
    if (!ruleGroup['details'].isEmpty) {
      builder.add(new Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle));
    }
    return builder;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: globalKey,
      body: GestureDetector(
        onLongPress: () {
          Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
          globalKey.currentState
            ..removeCurrentSnackBar()
            ..showSnackBar(SnackBar(content: Text('text copied')));
        },
        child: Container(
          margin: const EdgeInsets.symmetric(vertical: 3.0),
          child: new FlatButton(
            onPressed: () => print("Handle button press here"),
            color: Colors.white,
            padding: EdgeInsets.symmetric(horizontal: 0.0),
            child: new Stack(
              children: <Widget>[
                new Container(
                  margin: const EdgeInsets.symmetric(vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
                  child: new Container(
                    child: Padding(
                      padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
                      child: new Column(
                        crossAxisAlignment: CrossAxisAlignment.stretch,
                        children: _buildChildren(),
                      ),
                    ),
                  ),
                )
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Upvotes: 1

CopsOnRoad
CopsOnRoad

Reputation: 267474

I am not sure if your build() method is completed or you are yet to change it, because it consist of many widgets which are just redundant. Like there is no need to have Container in Container and further Padding along with a FlatButton which would make complete screen clickable. Also having Column won't be a good idea because your screen may overflow if you have more data. Use ListView instead.

So, if you were to take my advice, use this simple code that should provide you what you are really looking for. (See the build() method is of just 5 lines.

class RowRule extends StatelessWidget {
  final GlobalKey<ScaffoldState> globalKey = GlobalKey();

  final DocumentSnapshot ruleGroup;

  RowRule(this.ruleGroup);

  _buildChildren() {
    var builder = <Widget>[];
    if (!ruleGroup['label'].isEmpty) {
      builder.add(
        ListTile(
          title: Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle),
          onLongPress: () {
            globalKey.currentState
              ..removeCurrentSnackBar()
              ..showSnackBar(SnackBar(content: Text("Clicked")));
          },
        ),
      );
    }
    if (!ruleGroup['details'].isEmpty) {
      builder.add(
        ListTile(
          title: Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle),
          onLongPress: () {
            globalKey.currentState
              ..removeCurrentSnackBar()
              ..showSnackBar(SnackBar(content: Text("Clicked")));
          },
        ),
      );
    }
    return builder;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: globalKey,
      body: ListView(children: _buildChildren()),
    );
  }
}

Upvotes: 0

Related Questions