letsar / flutter_slidable

A Flutter implementation of slidable list item with directional slide actions.
MIT License
2.67k stars 572 forks source link

UI error when navigating back #227

Closed erperejildo closed 3 years ago

erperejildo commented 3 years ago

When the slidable widget is shown and I press back I see the hidden element during the navigation:

return ListView.separated(
              separatorBuilder: (context, index) => Divider(height: 0),
              itemCount: snapshot.data.docs.length,
              itemBuilder: (BuildContext context, int index) {
                final contact = snapshot.data.docs[index].data();
                contact["documentID"] =
                    snapshot.data.docs[0].data()["documentID"];
                return _contact(contact);
              },
            );
Widget _contact(contact) {
    return Slidable(
      actionPane: SlidableDrawerActionPane(),
      actionExtentRatio: 0.25,
      child: Container(
        child: ListTile(
          title: Text(contact["name"]),
          subtitle: contact["info"] != "" ? Text(contact["info"]) : null,
        ),
      ),
      actions: <Widget>[
        IconSlideAction(
          // caption: 'Edit',
          color: Theme.of(context).primaryColor,
          icon: LineIcons.pen,
          onTap: () async => await Navigator.of(context)
              .pushNamed(
            "/create-contact",
            arguments: CreateContactData(false, contact),
          )
              .then((val) {
            print(val);
          }),
        ),
      ],
      secondaryActions: <Widget>[
        Visibility(
          child: IconSlideAction(
            // caption: 'Website',
            color: Theme.of(context).primaryColor,
            icon: LineIcons.globe,
            onTap: () => PhoneContact().web(context, contact["web"]),
          ),
          visible: contact["web"].length > 11,
        ),
        IconSlideAction(
          // caption: 'Email',
          color: Theme.of(context).primaryColor,
          icon: LineIcons.envelope,
          onTap: () => PhoneContact().web(context, contact["email"]),
        ),
        IconSlideAction(
          // caption: 'Whatsapp',
          color: Theme.of(context).primaryColor,
          icon: LineIcons.comments,
          onTap: () => PhoneContact().whatsapp(context,
              contact["phone"]["phoneCode"] + contact["phone"]["phoneNumber"]),
        ),IconSlideAction(
          // caption: 'Whatsapp',
          color: Theme.of(context).primaryColor,
          icon: LineIcons.phone,
          onTap: () => PhoneContact().call(context,contact["phone"]["phoneCode"] +contact["phone"]["phoneNumber"]);
        ),
      ],
    );
}
letsar commented 3 years ago

Can you try with the 1.0 version?

erperejildo commented 3 years ago

Thanks, that worked

erperejildo commented 3 years ago

More questions after changing to this version @letsar :

how could I have a const background like I had before? backgroundColor: Theme.of(context).primaryColor,

and an onPressed function without having this error? I can't see any example in the docs The argument type 'void Function()' can't be assigned to the parameter type 'void Function(BuildContext)?'

how can I have a dynamic SlidableAction within a Visibility widget?

letsar commented 3 years ago

I don't understand the first question, can you provide the code before the 1.0 you used to set the background?

The function now takes a BuildContext parameter, because it can be useful to get the SlidableController here (with Slidable.of(context)). So you just have to add that parameter to your function.

What is the actual behavior if you use a Visibility widget above a SlidableAction? What did you expect?

erperejildo commented 3 years ago

I don't understand the first question, can you provide the code before the 1.0 you used to set the background?

The function now takes a BuildContext parameter, because it can be useful to get the SlidableController here (with Slidable.of(context)). So you just have to add that parameter to your function.

What is the actual behavior if you use a Visibility widget above a SlidableAction? What did you expect?

@letsar The code before 1.0 is the one I posted originally:

IconSlideAction(
          color: Theme.of(context).primaryColor,

Can you share an example of a real onPressed function? I tried different things but didn't work.

The error with Visibility is the same thing I got with the color: I cannot use a constant expression:

Screenshot 2021-07-19 at 11 56 07 Screenshot 2021-07-19 at 11 56 32
erperejildo commented 3 years ago

let me know if you need more info @letsar

letsar commented 3 years ago

I'm not sure I understood everything, so let me know if my suppositions are wrong. So according to your comments you tried to use the const keyword in front of an object which cannot be a constant because there are some properties that cannot be evaluated at compile time. For example you can't use Theme.of(context).primaryColor in a const widget because this expression can only be evaluated at runtime, same goes with contact["web].length > 11. If you want to use this value, simply remove the const keyword in front of the nearest parent widget constructor. Does this help?

erperejildo commented 3 years ago

I'm not sure I understood everything, so let me know if my suppositions are wrong. So according to your comments you tried to use the const keyword in front of an object which cannot be a constant because there are some properties that cannot be evaluated at compile time. For example you can't use Theme.of(context).primaryColor in a const widget because this expression can only be evaluated at runtime, same goes with contact["web].length > 11. If you want to use this value, simply remove the const keyword in front of the nearest parent widget constructor. Does this help?

that's correct but I don't have any const.

What about the onPressed? I checked your example but it's not complete: void doNothing(BuildContext context) {}

Cannot we have a simple onPressed like we do for the rest of the widgets? Is this problem related to the previous ones?

letsar commented 3 years ago

that's correct but I don't have any const.

That's strange, can you share the whole code, or a sample one where I can reproduced it?

Cannot we have a simple onPressed like we do for the rest of the widgets? Is this problem related to the previous ones?

I added the BuildContext here, so that we can do Slidable.of(context) without having to put the widget under a Builder. There is a common error I wanted users to avoid.

erperejildo commented 3 years ago

that's correct but I don't have any const.

That's strange, can you share the whole code, or a sample one where I can reproduced it?

import 'package:contacts_service/contacts_service.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:line_icons/line_icons.dart';
import 'package:my_rents/models/create_contact_data.dart';
import 'package:my_rents/models/phone_contact_list_data.dart';
import 'package:my_rents/services/firebase_profile.dart';

class ContactsListScreen extends StatefulWidget {
  @override
  _ContactsListScreenState createState() => _ContactsListScreenState();
}

class _ContactsListScreenState extends State<ContactsListScreen> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        iconTheme: IconThemeData(
          color: Theme.of(context).primaryColorDark,
        ),
        backgroundColor: Colors.transparent,
        elevation: 0.0,
        title: Text(
          translate('contacts_list.title'),
          style: TextStyle(color: Theme.of(context).primaryColorDark),
        ),
        actions: <Widget>[
          Padding(
            padding: EdgeInsets.only(right: 8.0),
            child: PlatformIconButton(
              icon: Icon(LineIcons.userPlus),
              onPressed: () {
                _askContactMethod(context);
              },
            ),
          ),
        ],
      ),
      body: StreamBuilder(
        stream: FirebaseProfile().getContactList(),
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          List<Widget> children;
          if (snapshot.hasData) {
            if (snapshot.data.docs.isEmpty) {
              return _noContactList(context);
            }
            return SingleChildScrollView(
              child: Column(
                children: [
                  ListView.separated(
                    // padding: const EdgeInsets.only(top: 0),
                    shrinkWrap: true,
                    physics: NeverScrollableScrollPhysics(),
                    separatorBuilder: (context, index) => Divider(height: 0),
                    itemCount: snapshot.data.docs.length,
                    itemBuilder: (BuildContext context, int index) {
                      final contact = snapshot.data.docs[index].data();
                      contact["documentID"] = snapshot.data.docs[index].id;
                      return _contact(contact);
                    },
                  ),
                  // Padding(
                  //   padding: const EdgeInsets.symmetric(vertical: 16.0),
                  //   child: Text("Slide contact horizontally for options",
                  //       style: Helpers.headerSubtitleStyle),
                  // ),
                ],
              ),
            );
          } else if (snapshot.hasError) {
            children = <Widget>[
              Icon(
                LineIcons.exclamationCircle,
                color: Colors.red,
                size: 60,
              ),
              Padding(
                padding: const EdgeInsets.only(top: 16),
                child: Text('Error: ${snapshot.error}'),
              )
            ];
          } else {
            children = <Widget>[
              Center(
                child: CircularProgressIndicator(),
              ),
            ];
          }
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: children,
            ),
          );
        },
      ),
    );
  }

  Widget _contact(contact) {
    // return ListTile(
    //   title: Text(contact["name"]),
    //   subtitle: Column(
    //     crossAxisAlignment: CrossAxisAlignment.start,
    //     children: [
    //       Visibility(
    //         visible: contact["info"] != "",
    //         child: Text(contact["info"]),
    //       ),
    //       SingleChildScrollView(
    //         scrollDirection: Axis.horizontal,
    //         child: Row(
    //           crossAxisAlignment: CrossAxisAlignment.start,
    //           children: [
    //             IconButton(
    //               icon: Icon(LineIcons.alternateTrash),
    //               color: Colors.red,
    //               onPressed: () async {
    //                 final deleted = await Helpers.deleteContactConfirmation(
    //                     context, contact["documentID"]);
    //               },
    //             ),
    //             IconButton(
    //               icon: Icon(LineIcons.pen),
    //               color: Theme.of(context).primaryColor,
    //               onPressed: () async {
    //                 // addContact(context, false, contact);
    //                 final editedContact = await Navigator.of(context)
    //                     .pushNamed(
    //                   "/create-contact",
    //                   arguments: CreateContactData(false, contact),
    //                 )
    //                     .then((val) {
    //                   print(val);
    //                 });
    //               },
    //             ),
    //             Visibility(
    //               visible: contact["web"].length > 11,
    //               child: IconButton(
    //                 icon: Icon(LineIcons.globe),
    //                 color: Theme.of(context).primaryColor,
    //                 onPressed: () async {
    //                   PhoneContact().web(context, contact["web"]);
    //                 },
    //               ),
    //             ),
    //             IconButton(
    //               icon: Icon(LineIcons.envelope),
    //               color: Theme.of(context).primaryColor,
    //               onPressed: () {
    //                 PhoneContact().email(context, contact["email"]);
    //               },
    //             ),
    //             IconButton(
    //               icon: Icon(LineIcons.whatSApp),
    //               color: Theme.of(context).primaryColor,
    //               onPressed: () {
    //                 PhoneContact().whatsapp(
    //                     context,
    //                     contact["phone"]["phoneCode"] +
    //                         contact["phone"]["phoneNumber"]);
    //               },
    //             ),
    //             IconButton(
    //               icon: Icon(LineIcons.phone),
    //               color: Theme.of(context).primaryColor,
    //               onPressed: () async {
    //                 PhoneContact().call(
    //                     context,
    //                     contact["phone"]["phoneCode"] +
    //                         contact["phone"]["phoneNumber"]);
    //               },
    //             ),
    //           ],
    //         ),
    //       ),
    //     ],
    //   ),
    // );

    return Slidable(
      key: const ValueKey(0),
      startActionPane: ActionPane(
        motion: const ScrollMotion(),
        dismissible: DismissiblePane(onDismissed: () {}),
        children: [
          SlidableAction(
              onPressed: null,
              //       onTap: () async {
              //         final deleted = await Helpers.deleteContactConfirmation(
              //             context, contact["documentID"]);
              //         if (deleted) {}
              //       },
              foregroundColor: Colors.blue,
              backgroundColor: Colors.transparent,
              icon: LineIcons.alternateTrash),
          SlidableAction(
            onPressed: null,
            //       onTap: () async => await Navigator.of(context)
            //           .pushNamed(
            //         "/create-contact",
            //         arguments: CreateContactData(false, contact),
            //       )
            foregroundColor: Colors.blue,
            backgroundColor: Colors.transparent,
            icon: LineIcons.pen,
          ),
        ],
      ),
      // The end action pane is the one at the right or the bottom side.
      endActionPane: const ActionPane(
        motion: ScrollMotion(),
        children: [
          // Visibility(
          //   child: SlidableAction(
          //     flex: 2,
          //     onPressed: null,
          //     // onTap: () => PhoneContact().web(context, contact["web"]),
          //     // backgroundColor: Color(0xFF7BC043),
          //     foregroundColor: Colors.blue,
          // backgroundColor: Colors.transparent,
          //     icon: LineIcons.globe,
          //   ),
          //   visible: contact["web"].length > 11,
          // ),
          SlidableAction(
            onPressed: null,
            //       onTap: () => PhoneContact().web(context, contact["email"]),
            foregroundColor: Theme.of(context).primaryColor,
            backgroundColor: Colors.transparent,
            icon: LineIcons.envelope,
          ),
          SlidableAction(
            onPressed: null,
            //       onTap: () => PhoneContact().whatsapp(context,
            //           contact["phone"]["phoneCode"] + contact["phone"]["phoneNumber"]),
            //     ),
            foregroundColor: Colors.blue,
            backgroundColor: Colors.transparent,
            icon: LineIcons.comments,
          ),
          SlidableAction(
            onPressed: null,
            //       onTap: () => PhoneContact().call(context,
            //           contact["phone"]["phoneCode"] + contact["phone"]["phoneNumber"]),
            foregroundColor: Colors.blue,
            backgroundColor: Colors.transparent,
            icon: LineIcons.phone,
          ),
        ],
      ),
      child: ListTile(
        title: Text(contact["name"]),
        subtitle: contact["info"] != "" ? Text(contact["info"]) : null,
      ),
    );
  }

  Widget _noContactList(BuildContext context) {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(10.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              translate('contacts_list.no_contacts'),
              textAlign: TextAlign.center,
              style: TextStyle(fontSize: 18.0),
            ),
            Padding(
              padding: const EdgeInsets.only(top: 10.0),
              child: Text(
                translate('contacts_list.no_contacts_note'),
                style: TextStyle(color: Colors.grey),
              ),
            )
          ],
        ),
      ),
    );
  }

  _askContactMethod(BuildContext oldContext) {
    return showPlatformDialog(
      context: context,
      builder: (context) => PlatformAlertDialog(
        title: Text(translate('contacts_list.create_contact')),
        content: Form(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              SizedBox(
                width: double.infinity,
                // height: double.infinity,
                child: PlatformButton(
                  child: Text(
                    translate('contacts_list.from_agenda'),
                    style: new TextStyle(fontSize: 16.0),
                  ),
                  onPressed: () {
                    // List<Contact> emptyList = [new Contact()];
                    Navigator.of(context).pop();
                    Navigator.of(context).pushNamed(
                      "/phone-contact-list",
                      arguments: PhoneContactListData(
                        insideLease: false,
                        contactsList: [], // TODO: check null before null, now ""
                        insideSearch: false,
                      ),
                    );
                  },
                ),
              ),
              SizedBox(
                width: double.infinity,
                // height: double.infinity,
                child: PlatformButton(
                  child: new Text(translate('contacts_list.from_manually'),
                      style: new TextStyle(fontSize: 16.0)),
                  onPressed: () async {
                    Navigator.of(context).pop();
                    Navigator.of(context).pushNamed(
                      "/create-contact",
                      arguments: CreateContactData(true, null),
                    );
                  },
                ),
              ),
            ],
          ),
        ),
        actions: <Widget>[
          TextButton(
            child: new PlatformText((translate('cancel'))),
            onPressed: () {
              Navigator.of(context).pop();
            },
          ),
        ],
      ),
    );
  }
}

class ItemsTile extends StatelessWidget {
  ItemsTile(this._title, this._items);
  final Iterable<Item> _items;
  final String _title;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        ListTile(title: Text(_title)),
        Column(
          children: _items
              .map(
                (i) => Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 16.0),
                  child: ListTile(
                    title: Text(i.label ?? ""),
                    trailing: Text(i.value ?? ""),
                  ),
                ),
              )
              .toList(),
        ),
      ],
    );
  }
}

Cannot we have a simple onPressed like we do for the rest of the widgets? Is this problem related to the previous ones?

I added the BuildContext here, so that we can do Slidable.of(context) without having to put the widget under a Builder. There is a common error I wanted users to avoid.

Could you please provide a full example of a real onPressed function? The example does nothing

letsar commented 3 years ago

that's correct but I don't have any const.

Line 222, your ActionPane is const. Remove it, and it will be alright.

Could you please provide a full example of a real onPressed function? The example does nothing

That's depend on what you want to do, if you want to call a method named launchTheRocket, you can do this for example:

SlidableAction(
  onPressed: (context) => launchTheRocket(),
),
erperejildo commented 3 years ago

ActionPane

that's correct but I don't have any const.

Line 222, your ActionPane is const. Remove it, and it will be alright.

Omg... I was looking everywhere except this widget.

Could you please provide a full example of a real onPressed function? The example does nothing

That's depend on what you want to do, if you want to call a method named launchTheRocket, you can do this for example:

SlidableAction(
  onPressed: (context) => launchTheRocket(),
),

Yeah, it was the problem above. I wasn't sure why a simple method was throwing that error. I just had to remove that const.

Thanks!