Dn-a / flutter_tags

With flutter tags you can create selectable or input tags that automatically adapt to the screen width
https://pub.dartlang.org/packages/flutter_tags
MIT License
507 stars 127 forks source link

Refresh #3

Closed jonfrank closed 5 years ago

jonfrank commented 5 years ago

I have a SelectableTags widget inside my build method. I also give the user the option to go to a page and edit tags using InputTags before returning to this main screen to select tags.

I can see it’s calling my method to rebuild the SelectableTags widget but what’s displayed doesn’t change. I’ve got it to print how many tags it is using when it rebuilds and so I know it’s picking up the updated tag list successfully, but what’s displayed doesn’t update.

Is there something I need to do to force the display to refresh??

monxarat commented 5 years ago

I think you can using setState() for refresh your list. Or you can post your code here.

Dn-a commented 5 years ago

@jonfrank if you have not resolved please post your code here.

jonfrank commented 5 years ago

Sorry for the delay.

I'm holding a singleton object called TagList which has an app-wide set of tags. And then in this particular screen I want to show all those tags with only some of them selected (the ones which apply to the particular object being displayed).

In my build method I create a list of tags to feed into SelectableTags which has the full list from TagList() but some are active and some are not. Debug shows that this gets created correctly each time there's a build - but the new set of tags doesn't get displayed.

So the particular issue for me is when I edit the app-wide tags in TagList() using the TagsPage widget (the last sample of code here) it returns to the main screen, the list of tags gets correctly recreated, but no display update. If I back up one level further, and then back into the screen, it refreshes OK.

[It is of course entirely possible that I'm doing something stupid which has nothing to do with flutter_tags... I am a flutter novice so apologies if so.]

Here's my TagList object:

class TagList {
  // singleton creation
  static final TagList _tagslist = new TagList._internal();
  factory TagList() {
    return _tagslist;
  }
  TagList._internal();

  List<Tag> tags;
  int nextId = 1;

  int nextUnusedIdFromTags(List<Tag> tags) {
    int result = 0;
    for (Tag t in tags) {
      if (t.id > result) {
        result = t.id;
      }
    }
    return ++result;
  }

  TagList setTags(List<Tag> tags) {
    this.tags = tags;
    this.nextId = nextUnusedIdFromTags(tags);
    return this;
  }

  TagList setTagsFromObject(TagList tags) {
    this.tags = tags.getTagsList();
    this.nextId = nextUnusedIdFromTags(this.tags);
    return this;
  }

  List<Tag> getTagsList() {
    return this.tags;
  }

  List<String> getStringTagsList() {
    final List<String> result = [];
    for (Tag t in this.tags) {
      result.add(t.title);
    }
    return result;
  }

  Tag tagAtIndex(int index) {
    return this.tags[index];
  }

  int numberOfTags() {
    if (tags == null) {
      return 0;
    } else {
      return tags.length;
    }
  }

  List<Tag> listWithTheseTagsActivated(List<String> aList) {
    // this returns the tag list but activates only those which are listed in the parameter
    // so we preserve proper ids but update active flag in the list copy
    List<Tag> result = [];
    for (Tag t in this.tags) {
      result.add(Tag(
        title: t.title,
        id: t.id,
        active: aList.contains(t.title),
      ));
    }
    return result;
  }

  void addStringTag(String toAdd) {
    Tag newTag = Tag(title: toAdd, active: true, id: nextId++);
    tags.add(newTag);
  }

  bool deleteTag(Tag toDel) {
    int index = this.tags.indexOf(toDel);
    if (index == -1) {
      return false;
    } else {
      this.tags.removeRange(index, index + 1);
      return true;
    }
  }

  bool deleteStringTag(String toDel) {
    Tag tagToDelete;
    for (Tag t in this.tags) {
      if (t.title == toDel) {
        tagToDelete = t;
        break;
      }
    }
    if (tagToDelete == null) {
      return false;
    } else {
      return deleteTag(tagToDelete);
    }
  }
}

And then my display widget which doesn't get refreshed properly looks like this:

  @override
  Widget build(BuildContext context) {
    tagSet = TagList().listWithTheseTagsActivated(tags); // tags comes from the object being displayed
    return ListView(
      children: [
        Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
...
              Padding(
                padding:
                    const EdgeInsets.symmetric(vertical: 8.0, horizontal: 25.0),
                child: SelectableTags(
                  tags: tagSet,
                  columns: 2,
                  fontSize: 12,
                  symmetry: true,
                  activeColor: Colors.greenAccent,
                  textActiveColor: Colors.black,
                  backgroundContainer: Colors.transparent,
                  alignment: MainAxisAlignment.start,
                  onPressed: (Tag tag) {
                    for (Tag t in tagSet) {
                      if (t.title == tag.title) {
                        t.active = !(t.active);
                      }
                    }
                  },
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }

TagsPage widget:

import 'package:flutter_tags/input_tags.dart';
import 'package:flutter/material.dart';
import 'package:casebook/model/case.dart';

class TagsPage extends StatefulWidget {
  @override
  _TagsPageState createState() => _TagsPageState();
}

class _TagsPageState extends State<TagsPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          leading: IconButton(
            icon: Icon(Icons.chevron_left),
            onPressed: () => Navigator.of(context).pop({'data': 'test'}),
          ),
          title: Text("Tags"),
        ),
        body: Container(
          color: Colors.lime,
          child: Center(
            child: Container(
              padding: const EdgeInsets.all(32.0),
              child: ListView(
                children: [
                  Text(
                    "Delete or insert tags",
                  ),
                  InputTags(
                    tags: TagList().getStringTagsList(),
                    columns: 2,
                    fontSize: 16,
                    symmetry: true,
                    backgroundContainer: Colors.transparent,
                    alignment: MainAxisAlignment.start,
                    onDelete: (tag) {
                      setState(() {
                        if (!CaseList().tagIsUsed(tag)) {
                          TagList().deleteStringTag(tag);
                        }
                      });
                    },
                    onInsert: (tag) {
                      setState(() {
                        TagList().addStringTag(tag);
                      });
                    },
                  )
                ],
              ),
            ),
          ),
        ));
  }
}
Dn-a commented 5 years ago

@jonfrank are you referring to this method?

onPressed: (Tag tag) {
                    for (Tag t in tagSet) {
                      if (t.title == tag.title) {
                        t.active = !(t.active);
                      }
                    }
                  },

I think the problem is due to the loss of the reference to the List

List<Tag> listWithTheseTagsActivated(List<String> aList) {
    // this returns the tag list but activates only those which are listed in the parameter
    // so we preserve proper ids but update active flag in the list copy
    List<Tag> result = [];
    for (Tag t in this.tags) {
      result.add(Tag(
        title: t.title,
        id: t.id,
        active: aList.contains(t.title),
      ));
    }
    return result;
  }