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

Clicking on TagsTextField causes rebuild #60

Closed bartonhammond closed 4 years ago

bartonhammond commented 4 years ago

I am using this class in two widgets. In one it works fine, in the other not even though they use it the same way.

When I click on the TagsTextField, the widget rebuilds and loses focus and no data can be entered. I've compared the two containing widget trees and cant see any difference.

I've seen SO issues like this: https://stackoverflow.com/questions/56412548/flutter-textfield-rebuilds-widget-on-focus - but I don't see how to use a GlobalKey w/ TagTextField.

Everything about flutter_tags has been working for me except this one thing which I'm baffled about. Any idea?

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_tags/flutter_tags.dart';

import 'package:voiceClient/constants/mfv.i18n.dart';
import 'package:voiceClient/constants/strings.dart';

import 'package:voiceClient/services/service_locator.dart';

class TagsWidget extends StatefulWidget {
  const TagsWidget(
      {Key key,
      @required this.allTags,
      @required this.tags,
      this.fontSize = 16,
      this.height = 200,
      this.updatedAble = true})
      : super(key: key);
  final List<String> allTags;
  final List<String> tags;
  final double fontSize;
  final double height;
  final bool updatedAble;

  @override
  _TagsWidgetState createState() => _TagsWidgetState();
}

class _TagsWidgetState extends State<TagsWidget> {
  bool _showAllTags = false;

  TagsTextField getTextField() {
    if (widget.updatedAble) {
      return TagsTextField(
        autofocus: false,
        hintText: Strings.addTagHere.i18n,
        textStyle: TextStyle(
          fontSize: widget.fontSize,
        ),
        enabled: true,
        constraintSuggestion: false,
        suggestions: null,
        onSubmitted: (String str) {
          setState(() {
            widget.tags.add(str);
          });
        },
      );
    } else {
      return null;
    }
  }

  ItemTagsRemoveButton getRemoveButton(int index) {
    if (widget.updatedAble) {
      return ItemTagsRemoveButton(
        backgroundColor: Colors.black,
        onRemoved: () {
          setState(() {
            widget.tags.removeAt(index);
          });
          return true;
        },
      );
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    const ItemTagsCombine combine = ItemTagsCombine.onlyText;

    return Card(
      margin: EdgeInsets.all(0),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Container(
            child: Tags(
              key: Key('tags'),
              symmetry: false,
              columns: 4,
              horizontalScroll: false,
              verticalDirection: VerticalDirection.up,
              textDirection: TextDirection.rtl,
              heightHorizontalScroll: 60 * (widget.fontSize / 14),
              textField: getTextField(),
              itemCount: widget.tags.length,
              itemBuilder: (index) {
                final String item = widget.tags[index];

                return GestureDetector(
                    child: ItemTags(
                  key: Key(index.toString()),
                  index: index,
                  title: item,
                  pressEnabled: false,
                  activeColor: Color(0xff00bcd4),
                  combine: combine,
                  image: null,
                  icon: null,
                  removeButton: getRemoveButton(index),
                  textScaleFactor:
                      utf8.encode(item.substring(0, 1)).length > 2 ? 0.8 : 1,
                  textStyle: TextStyle(
                    fontSize: widget.fontSize,
                  ),
                ));
              },
            ),
          ),
          SizedBox(
            height: 8,
          ),
          Container(
            padding: EdgeInsets.all(20),
            child: Column(
              children: <Widget>[
                Text(Strings.showAllTags.i18n,
                    style:
                        TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
                Checkbox(
                  value: _showAllTags,
                  onChanged: (bool show) {
                    if (show) {
                      widget.allTags.forEach(widget.tags.add);
                    } else {
                      widget.allTags.forEach(widget.tags.remove);
                    }

                    setState(() {
                      _showAllTags = show;
                    });
                  },
                ),
                Divider(
                  color: Colors.blueGrey,
                ),
                Padding(
                  padding: EdgeInsets.symmetric(vertical: 20),
                  child: Text(''),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
bartonhammond commented 4 years ago

I changed the Tags to be more consistent w/ the example code but still same problem

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_tags/flutter_tags.dart';

import 'package:voiceClient/constants/mfv.i18n.dart';
import 'package:voiceClient/constants/strings.dart';

Widget getTags(
  List<String> allTags,
  List<String> tags,
  void Function(String) onTagAdd,
  void Function(int) onTagRemove, {
  double fontSize = 16,
  double height = 200,
  bool updatedAble = true,
}) {
  TagsTextField getTextField() {
    if (updatedAble) {
      return TagsTextField(
        autofocus: false,
        hintText: Strings.addTagHere.i18n,
        textStyle: TextStyle(
          fontSize: fontSize,
        ),
        enabled: true,
        constraintSuggestion: false,
        suggestions: null,
        onSubmitted: onTagAdd,
      );
    } else {
      return null;
    }
  }

  ItemTagsRemoveButton getRemoveButton(int index) {
    if (updatedAble) {
      return ItemTagsRemoveButton(
          backgroundColor: Colors.black,
          onRemoved: () {
            onTagRemove(index);
            return true;
          });
    }
    return null;
  }

  return Tags(
    key: Key('tags'),
    symmetry: false,
    columns: 4,
    horizontalScroll: false,
    verticalDirection: VerticalDirection.up,
    textDirection: TextDirection.rtl,
    heightHorizontalScroll: 60 * (fontSize / 14),
    textField: getTextField(),
    itemCount: tags.length,
    itemBuilder: (index) {
      final String item = tags[index];

      return GestureDetector(
          child: ItemTags(
        key: Key(index.toString()),
        index: index,
        title: item,
        pressEnabled: false,
        activeColor: Color(0xff00bcd4),
        combine: ItemTagsCombine.onlyText,
        image: null,
        icon: null,
        removeButton: getRemoveButton(index),
        textScaleFactor: utf8.encode(item.substring(0, 1)).length > 2 ? 0.8 : 1,
        textStyle: TextStyle(
          fontSize: fontSize,
        ),
      ));
    },
  );
}
bartonhammond commented 4 years ago

Boy, do I feel stupid. It turned out I was using device_preview (which is awesome in helping UI device sizeing) with iPhone 8 running inside iPad. I turned it off as below, and everything is fine.

runApp(DevicePreview(
    enabled: false,   
    builder: (BuildContext context) => MyApp(),
  ));