Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flutter: CircleAvatar with fallback text

Tags:

flutter

dart

I'm learning Flutter and would like to make a Widget just like the built-in CircleAvatar. However, I would like the behaviour to be

  • specify both an Image (NetworkImage) and initials (ie, BB)
  • while the image isn't loaded, show the initials
  • if the image does load, show the image and remove the initials

The following code sort of works, but when used in the Chat demo it falls apart as multiple MyAvatars are added. Breakpointing on initState shows that it is always called with the first message text that is entered - not what I expected. It also flickers as images "reload". It appears that the widgets are being reused in a way I don't understand.

class MyAvatar extends StatefulWidget {                     
  NetworkImage image;
  MyAvatar({this.text}) {
    debugPrint("MyAvatar " + this.text);
    if (text.contains('fun')) {
      this.image = new NetworkImage("https://cdn3.iconfinder.com/data/icons/minicons-for-web-sites/24/minicons2-14-512.png");
    }
  }
  final String text;
  @override                                                        
  MyAvatarState createState() {
    return new MyAvatarState();
  }                    
}

class MyAvatarState extends State<MyAvatar> {
  bool showImage = false;
  @override
  initState() {
    super.initState();
    if (widget.image != null) {
      var completer = widget.image.load(widget.image);
      completer.addListener((info, sync) {
        setState(() {
          showImage = true;
        });
      });
    }
  }

  @override 
  Widget build(BuildContext context) {
    return !showImage ? new CircleAvatar(radius: 40.0, child: new Text(widget.text[0]))
        : new CircleAvatar(radius: 40.0, backgroundImage: widget.image);
  }
}

I'm still having trouble - full code

import 'package:flutter/material.dart';
// Modify the ChatScreen class definition to extend StatefulWidget.

class ChatScreen extends StatefulWidget {                     //modified
  ChatScreen() {
    debugPrint("ChatScreen - called on hot reload");
  }
  @override                                                        //new
  State createState() {
    debugPrint("NOT on hot reload");
    return new ChatScreenState();
  }                    //new
}

// Add the ChatScreenState class definition in main.dart.

class ChatScreenState extends State<ChatScreen> {
  final List<ChatMessage> _messages = <ChatMessage>[];
  final TextEditingController _textController = new TextEditingController(); //new
  ChatScreenState() {
    debugPrint("ChatScreenState - not called on hot reload");
  }

  @override //new
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: new Text("Friendlychat")),
      body: new Column(                                        //modified
          children: <Widget>[                                         //new
            new Flexible(                                               //new
                child: new ListView.builder(                              //new
                  padding: new EdgeInsets.all(8.0),                       //new
                  reverse: true,                                          //new
                  itemBuilder: (_, int index) => _messages[index],        //new
                  itemCount: _messages.length,                            //new
                )                                                         //new
            ),                                                          //new
            new Divider(height: 1.0),                                   //new
            new Container(                                              //new
              decoration: new BoxDecoration(
                  color: Theme.of(context).cardColor),                   //new
              child: _buildTextComposer(),                         //modified
            ),                                                          //new
          ]                                                            //new
      ),                                                             //new
    );
  }

  Widget _buildTextComposer() {

    return new IconTheme(
        data: new IconThemeData(color: Theme
            .of(context)
            .accentColor),
        child:
        new Container(
            margin: const EdgeInsets.symmetric(horizontal: 8.0),
            child: new Row(
                children: <Widget>[
                  new Container( //new
                    margin: new EdgeInsets.symmetric(horizontal: 4.0), //new
                    child: new IconButton( //new
                        icon: new Icon(Icons.send),
                        onPressed: () =>
                            _handleSubmitted(_textController.text)), //new
                  ),
                  new Flexible(
                      child: new TextField(
                        controller: _textController,
                        onSubmitted: _handleSubmitted,
                        decoration: new InputDecoration.collapsed(
                            hintText: "Send a message"),
                      )
                  ),
                ])
        )
    );
  }

  void _handleSubmitted(String text) {


    _textController.clear();
    ChatMessage message = new ChatMessage(text: text);
    setState(() {
      _messages.insert(0, message);
    });
  }
}

const String _name = "Hardcoded Name";

class ChatMessage extends StatelessWidget {
  ChatMessage({this.text, this.image, this.useImage});
  final String text;
  final NetworkImage image;
  final Map useImage;
  @override
  Widget build(BuildContext context) {
    var use = true; //useImage != null && useImage['use'];

    var image = new NetworkImage("https://cdn3.iconfinder.com/data/icons/minicons-for-web-sites/24/minicons2-14-512.png");
    if (text.contains('bad')) {
      image = new NetworkImage("https://cdn3.iconfinder.com/data/icons/minicons-for-web-sites/24/minicons2-14-512.pngz");
    }
    return new Container(
      margin: const EdgeInsets.symmetric(vertical: 10.0),
      child: new Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          new Container(
            margin: const EdgeInsets.only(right: 16.0),
            child : new CustomCircleAvatar(initials: text[0], myImage: image)
          ),
          new Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              new Text(_name, style: Theme.of(context).textTheme.subhead),
              new Container(
                margin: const EdgeInsets.only(top: 5.0),
                child: new Text(text),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class CustomCircleAvatar extends StatefulWidget {
  NetworkImage myImage;

  String initials;


  CustomCircleAvatar({this.myImage, this.initials}) {
    debugPrint(initials);
  }

  @override
  _CustomCircleAvatarState createState() => new _CustomCircleAvatarState();
}

class _CustomCircleAvatarState extends State<CustomCircleAvatar>{

  bool _checkLoading = true;

  @override
  void initState() {
    if (widget.myImage != null) {
      widget.myImage.resolve(new ImageConfiguration()).addListener((image, sync) {
        if (mounted && image != null) {
          setState(() {
            _checkLoading = false;
          });
        }
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return _checkLoading == true ? new CircleAvatar(child: new Text(widget.initials))
        : new CircleAvatar(backgroundImage: widget.myImage);
  }
}

Enter 'fun' as a message, then 'bad' as the second - image The idea is that depending on what you enter, different images might load (or not). In the 'failed to load' case, the initials should remain.

like image 858
bradbeveridge Avatar asked Oct 18 '17 11:10

bradbeveridge


1 Answers

You can achieve this functionality by adding a listener to ImageStream that you can obtain from ImageConfiguration,

Here, I am feeding the same data to my ListView you can of course customize this yourself by adding a List of images and initials as a field in any class and use ListView.builder instead to be able to loop on them by index.

enter image description here

class CustomCircleAvatar extends StatefulWidget {
  NetworkImage myImage;

  String initials;


  CustomCircleAvatar({this.myImage, this.initials});

  @override
  _CustomCircleAvatarState createState() => new _CustomCircleAvatarState();
}

class _CustomCircleAvatarState extends State<CustomCircleAvatar>{

  bool _checkLoading = true;

  @override
  void initState() {
    widget.myImage.resolve(new ImageConfiguration()).addListener((_, __) {
      if (mounted) {
        setState(() {
          _checkLoading = false;
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return _checkLoading == true ? new CircleAvatar(
        child: new Text(widget.initials)) : new CircleAvatar(
      backgroundImage: widget.myImage,);
  }
}

Now you can use it like this:

void main() {
  runApp(new MaterialApp (home: new MyApp()));
}

    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => new _MyAppState();
    }

    class _MyAppState extends State<MyApp> {
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          appBar: new AppBar(title: new Text("Custom Circle Avatar"),),
          body: new ListView(children: new List.generate(20, (int index) {
            return new Container(
              height: 100.0,
              width: 100.0,
              child: new CustomCircleAvatar(myImage: new NetworkImage(
                  "https://www.doginni.cz/front_path/images/dog_circle.png"),
                initials: "Dog",
              ),
            );
          }),),
        );
      }
    }
like image 55
Shady Aziza Avatar answered Nov 15 '22 10:11

Shady Aziza