1

I am working on a Flutter chat implementation, where for each chat left-aligned message I would like the ListTile to start with a Column that will contain 2 widgets:

  • the user avatar image
  • their name below

The issue I am running into is that the Column will not expand, and image gets cropped. Here is an example. I have tried 2 variations, where I try to set height/width at 70 and then at 100. I get the same result in both cases. When I add a line break between first and last name, last name goes "off-screen".

What is the correct way to implement this kind of layout, such that the ListTile expands only as much as it needs to for the provided content?

enter image description here

Below is the code I tried to make this work.

Notice this line here: bool isNameShown = false; . Use this to toggle the state for the error case.

This is a working standalone that can be copied into a new .dart file and run. Thank you for taking a look.

import 'package:flutter/material.dart';

void main() => runApp(AvatarChatListDemo());

class AvatarChatListDemo extends StatefulWidget {
  @override
  _AvatarChatListDemoState createState() => _AvatarChatListDemoState();
}

class _AvatarChatListDemoState extends State<AvatarChatListDemo> {

  var imagePath = 'assets/images/lion.jpeg';
  List chatHistory = ['Hello there Sam', 'Hi Bob, Good day '];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(appBar: null, body: getChatMessageList()));
  }

  Widget getChatMessageList() {
    bool isNameShown = true; // Use this to toggle the state for the error case.

    return ListView.builder(
      itemCount: chatHistory.length,
      itemBuilder: (BuildContext context, int index) {
        return getNextChatListTile(index, isNameShown);
      },
    );
  }

  double dim = 100;
  ListTile getNextChatListTile(int chatMessageIndex, bool isNameShown) {
    var chatMessage = chatHistory[chatMessageIndex];

    Image leftSideAvatarImage =
        Image.asset(imagePath, width: dim, height: dim, fit: BoxFit.cover);

    List<Widget> columnWidgets = [
      Expanded(child: leftSideAvatarImage),
    ];

    if (isNameShown) {
      columnWidgets.add(Expanded(
          child: Text('Matt Houston',
              style: TextStyle(color: Colors.green, fontSize: 20))));
    }

    Column leftSideAvatarColumn = Column(
        mainAxisSize: MainAxisSize.max,
        mainAxisAlignment: MainAxisAlignment.start,
        children: columnWidgets);

    Container messageContainer = Container(
        padding: EdgeInsets.all(15),
        color: Colors.white,
        child: Text(chatMessage, style: TextStyle(fontSize: 20)));

    return ListTile(
        contentPadding: EdgeInsets.all(5),
        leading: leftSideAvatarColumn,
        title: messageContainer);
  }
}   
Gene Bo
  • 11,284
  • 8
  • 90
  • 137
  • So to be clear, you want the ListTile row to expand to fit both the name and image when name is shown? if not try setting `dense:true`, in the your ListTile widget and see if that helps – Mohammad Assad Arshad Jun 05 '20 at 18:28
  • Correct, as you state: ListTile row should *" ... expand to fit both the name and image when name is shown"*, and .. image should not be cropped in any case – Gene Bo Jun 05 '20 at 19:34
  • did u tried `IntrinsicHeight` https://stackoverflow.com/questions/51326170/flutter-layout-row-column-share-width-expand-height – GJJ2019 Jun 07 '20 at 19:24
  • Hi, I did try `IntrinsicHeight`. Thanks for the suggestion, I like the idea of how it can be used, however, I wasn't sure the best way to configure it. I added an answer here with details below. – Gene Bo Jun 07 '20 at 21:40

2 Answers2

1

Like that?

enter image description here

import 'package:flutter/material.dart';

void main() => runApp(AvatarChatListDemo());

class AvatarChatListDemo extends StatefulWidget {
  @override
  _AvatarChatListDemoState createState() => _AvatarChatListDemoState();
}

class _AvatarChatListDemoState extends State<AvatarChatListDemo> {
  var imagePath = 'assets/images/lion.jpg';
  List chatHistory = ['Hello there Sam', 'Hi Bob, Good day '];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(appBar: null, body: getChatMessageList()));
  }

  Widget getChatMessageList() {
    bool isNameShown = true; // Use this to toggle the state for the error case.

    return ListView.builder(
      itemCount: chatHistory.length,
      itemBuilder: (BuildContext context, int index) {
        return getNextChatListTile(index, isNameShown);
      },
    );
  }

  double dim = 100;
  Widget getNextChatListTile(int chatMessageIndex, bool isNameShown) {
    var chatMessage = chatHistory[chatMessageIndex];

    Image leftSideAvatarImage =
        Image.asset(imagePath, width: dim, height: dim, fit: BoxFit.contain);

    List<Widget> columnWidgets = [
      leftSideAvatarImage,
    ];

    if (isNameShown) {
      columnWidgets.add(
        Text(
          'Matt Houston',
          style: TextStyle(color: Colors.green, fontSize: 15),
        ),
      );
    }

    var leftSideAvatarColumn = ConstrainedBox(
      constraints: BoxConstraints(maxWidth: 100),
      child: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.start,
          children: columnWidgets),
    );

    Container messageContainer = Container(
        padding: EdgeInsets.all(15),
        color: Colors.white,
        child: Text(chatMessage, style: TextStyle(fontSize: 20)));

    return Padding(
        padding: const EdgeInsets.all(5.0),
        child: Row(
          children: <Widget>[leftSideAvatarColumn, messageContainer],
        ));
  }
}
Kherel
  • 14,882
  • 5
  • 50
  • 80
  • Nice, that fixed it, Thank you. I see the main diffs from my original attempt are: 1) use of `fit: BoxFit.contain`, 2) `ConstrainedBox` wraps the `BoxConstraints`, and 3) instead of `ListTile` we return just a `Row` inside `Padding` – Gene Bo Jun 07 '20 at 21:42
  • 1
    There are several types of widgets, Pre-styled widgets usually cupertino or material widgets. `ListTile`, `AppBar`, buttons, sliders, ...etc. Sometimes they have customization options, but usually they are very strict. In the `html` world it would be `html` frameworks like `bootstrap`. Basis layout widgets: `Container`, `Text`, `ListView`, `Row`, `Column`. These widgets are much more flexible. In `html` it’s `html tags` with `css`. – Kherel Jun 08 '20 at 07:03
  • 1
    Functional widgets: different state management widget + animation + different widgets to work events (like touch or scroll), these widgets exist to add extra functionality in the `html` word it’s `js`. So.. usually if you can't do something with pre-styled widgets, the easiest way to solve it is to create your own widget by combination of the basis layout widgets. – Kherel Jun 08 '20 at 07:04
  • 1
    Your point about the Pre-Styled widgets and the analogy with HTML tags, that is very helpful. I was under the impression I "must"/strongly-should use a ListTile with the ListView. I have to remember that a Widget in Flutter is a Widget, and I can use widgets interchangeably. – Gene Bo Jun 09 '20 at 20:23
  • I'm glad that I could help you. Happy coding. – Kherel Jun 10 '20 at 07:50
0

Below is the update with IntrinsicHeight. I'm not sure if I applied it correctly or where it can be optimized. The issue is that it distributes the space more evenly, and doesn't minimize the surrounding empty space enough:

return ListTile(
    contentPadding: EdgeInsets.all(5),
    // leading: leftSideAvatarColumn,
    // title: messageContainer);
    title: IntrinsicHeight(
      child: Row(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
        Expanded(
          child: leftSideAvatarColumn,
        ),
        Expanded(child: messageContainer),
      ]),
    ));

enter image description here

Although I do like it because I have to move less code around, @Kherel's answer is a more accurate solution because it manages both the vertical and horizontal space better.. all of that empty space is compressed and left-aligned.

For future reference - if there's a way to optimize this to do the same I think it would be interesting for anyone working on this type of scenario. If someone else needs it, I suppose they can make a new post.

Gene Bo
  • 11,284
  • 8
  • 90
  • 137