Implementing KakaoTalk Chat Input Box in Flutter

Lee young-jun
10 min readMay 9, 2024

--

My new app contains a chat feature. So I wanted to mimic the chatting input box of KakaoTalk.

Currently My Screen is like following.

And the code is

bottomNavigationBar: Padding(
padding: MediaQuery.of(context).viewInsets,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: TextField(
keyboardType: TextInputType.multiline,
minLines: 1,
maxLines: 5,
decoration:
const InputDecoration(hintText: '파티원들에게 하고 싶은 말'),
controller: viewModel.messageController,
onSubmitted: viewModel.onReturnKeyPressed,
)),
ElevatedButton(
onPressed: viewModel.onSendMessage,
child: const Text('@'))
],
),
),

Let’s get started

Background Color

I first discovered the color value of background.

So how to set background color of a Widget?
Most components on other platforms have a style for it.

However, Flutter can’t do it. To set the background, we have to use the Container widget like this.

child: Container(
color: Colors.yellow,
child: Row(
...

To match the color to 2c2c2e, we should find a way to make a Color from the hex value. So, I attempted like this, but it wasn’t working.

color: const Color(0x2c2c2e)

Fortunately, there is a hint in the official document for Color. It means the default opacity of color is zero.

With an opaque color value, we can match the background color to KakaoTalk.

Text Color

The new background color made the text field placeholder difficult to read. Therefore, we should find a way to change the text color, not the background.

Initially, I checked the properties of the constructor, but found only a color property for the cursor.

Where is color for text?? I discovered that TextField has a Style property that can be used to set the text color.

style: const TextStyle(color: Colors.white),

Placeholder Color

However I didn’t type any text yet. How can we set the color of the placeholder, not the actual text?

You discovered that setting the placeholder text color is straightforward through the use of the hintText property along with hintStyle.

The style for hintStyle is simply TextStyle, enabling us to easily set the color.

decoration: const InputDecoration(
hintText: '파티원들에게 하고 싶은 말',
hintStyle: TextStyle(color: Colors.grey)),

TextField Background Color

KakoaTalk’s background color of the input box is different to the container. So should we use Container again?

Nope! TextField provides another approach. As we’ve seen the text color, we can set it using properties.

There is fill… properties. By setting filled parameter to true, we can fill TextField with fillColor.

decoration: const InputDecoration(
hintText: '파티원들에게 하고 싶은 말',
hintStyle: TextStyle(color: Colors.grey),
filled: true,
fillColor: Color(0xFF38383a)),

Padding

The Input box had spacing between it’s container. I measured it and it was 8 pixel.

Unlike other platforms that use margin to create external space, Flutter uses padding. Therefore, to implement this spacing, you need to wrap the TextField with Padding.

Since the horizontal and vertical margins differ,

using EdgeInsets.all is not suitable. Instead, you can use EdgeInsets.fromLTRB to set the margins for all directions individually.

Rounded Corner

To achieve more rounded corners for the TextField, which already has slightly rounded corners, we can utilize the InputDecoration property..

Other platforms might offer a border property with a cornerRadius attribute for this purpose.

In Flutter, we can use InputDecoration to specify an OutlineInputBorder, which includes a borderRadius property.

decoration: InputDecoration(
...,
border: OutlineInputBorder(
borderSide: const BorderSide(
width: 2, color: Color(0xFF414143)),
borderRadius: BorderRadius.circular(30.0))),

This allows us to define the degree of roundness for the corners of the TextField.

Inner Padding

We didn’t set padding to inside of the text field, however there was spacing. InputDecoration has special contentPadding. So we can simply set it to zero.

contentPadding: EdgeInsets.zero,

But our target has paddings not zero.

Therefore we should use again.

contentPadding: const EdgeInsets.fromLTRB(18, 0, 18, 0),

Right Button

To replicate KakaoTalk’s send button placement inside the input box,

we can utilize the suffixIcon property of the TextField.

This property allows you to add an icon or any Widget to the right side of the TextField. Although you might initially consider setting only an Icon,

you have the flexibility to embed a button within this space since the type of suffixIcon is Widget.

suffixIcon: IconButton(icon: const Icon(
Icons.arrow_upward),
onPressed: viewModel.onSendMessage),

However we have to set background of icon. There is no property to set background, so we should use Container again.

suffixIcon: Container(
color: Colors.yellow,
child: IconButton(...),
),

Rounded Button

To make the TextField rounded, we use a Container for the IconButton since the IconButton itself doesn’t support decoration.

The decoration is done using BoxDecoration, not InputDecoration.

suffixIcon: Container(
// color: Colors.yellow,
decoration: BoxDecoration(
color: Colors.yellow,
borderRadius: BorderRadius.circular(30)),

However, if the BoxDecoration is used, the Container cannot have its own color property set — this color must be specified within the BoxDecoration. If the color is not specified within the BoxDecoration, it could cause the app to crash.

Resizing Right Icon

Now the button is circular, but there is some space around it.

How to resize the button? We should use SizedBox container? There is one option BoxConstraints. Applying BoxConstraints to the button gives us the ability to set minimum and maximum limits on its size,

suffixIconConstraints: const BoxConstraints(
maxHeight: 40, maxWidth: 40),

Spacing Right Icon

There was still one more issue. We need to add space on the right of the button. So I attempted to append padding into Container, but it is not working.

suffixIcon: Container(
padding:
const EdgeInsets.fromLTRB(0, 0, 8, 0)

I wrapped the Container with Padding ,

suffixIcon: Padding(
padding:
const EdgeInsetsDirectional.only(end: 4),
child: Container(

however, if this approach reduces the button size,

a better solution would be to increase the width of the SuffixIcon.

suffixIconConstraints: const BoxConstraints(
maxHeight: 40, maxWidth: 40 + 4),

Let me know better ways..

Hide Right Button

To make KakaoTalk’s send button invisible to prevent sending an empty message, we can set the suffixIcon to null when you want to hide it.

suffixIcon: viewModel
.messageController.text.isNotEmpty
? ...
: null,

text of TextFieldController is nested, so viewModel won’t know that text was changed. To solve this problem, we should notify the viewModel manually.

onChanged: viewModel.onMessageInputUpdated,

class ...ViewModel {
onNewMessageChanged(String message) {
notifyListeners();
}
}

Or we should replace TextFieldController with string property. In my code, the controller is useless now.

String _newMessageText = "";

String get newMessageText => _newMessageText;

setNewMessageText(String value) {
_newMessageText = value;
notifyListeners();
}

Clear TextField

After sending a message, to clear the TextField for the next message input, I reattached the TextEditingController again

controller: messageEditingController,

and called its clear method after the message sending method.

child: IconButton(
icon: const Icon(
Icons.arrow_upward),
onPressed: () {
viewModel.onSendMessage();
messageEditingController.clear();
}),

However, this approach doesn’t automatically hide the send button as previously mentioned. To resolve this, I cleared the newMessageText at the end of the onSendMessage function.

onSendMessage() {
...

clearNewMessageText();
}

Button Alignment

Now that the send button is properly configured, an issue arises with multiline message inputs.

Various solutions available online were tried, but they resulted in the TextField being stretched due to the suffixIcon’s behavior.

Consequently, the approach was shifted from using suffixIcon to using suffix,

suffix: viewModel.newMessageText.isNotEmpty ? Padding( ...

essentially removing the icon from the suffixIcon setup.

However, finding a solution for vertically aligning the suffix or suffixIcon proved difficult.

Ultimately, the decision was made to overlay the suffix button on the TextField, as a workaround for the vertical alignment challenge.

Stack

How to overlay another component on components? We can list vertically and horizontally using Column and Row. Stack is like Column or Row for Z-Axis.

I first wrapped TextField into Stack. There is no shortcut for Stack, so I choose Column.

and replaced Column with Stack

child: Stack(
children: [
TextField(

Next, I moved suffixIcon out of TextField, but I encountered type error.

So I created ChatButtonContainer to contain Buttons.

class ChatButtonContainer extends StatelessWidget {
const ChatButtonContainer({super.key, required this.children});

final List<Widget> children;

@override
Widget build(BuildContext context) {
return Row(children: children);
}
}

And replace with it.

children: [
TextField(...),
ChatButtonContainer(children: [
ChatSendButton(onPressed: () {
viewModel.onSendMessage();
messageEditingController.clear();
})
])

This way has a problem. The button is always visible.

So should I use ternary operator again? Fortunately we can use this trick.

ChatButtonContainer(children: [
if (viewModel.newMessageText.isNotEmpty)
ChatSendButton(...

No need to filter widget list. There is only a task to bottom align the button again.

First align the buttons right end.

Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: children);

Next, align to bottom using Column.

Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: children),
],
);

Ah ahahaha Column also cannot solve the problem.

Positioned

So we have to give up?? There is one more thing. Positioned is our hero.

Widget build(BuildContext context) {
return Positioned(
bottom: 0,
right: 0,
child: Row(children: children));
}

I won’t explain about Positioned , you already know it by the name.

With adjusting the size and content padding. The final result looks good.

contentPadding: const EdgeInsets.fromLTRB(18, 0, 44, 0),
...
Positioned(bottom: 4, right: 4

There are additional buttons in Kakao-Talk, however my app doesn’t support them now. So I finished here.

If you found this post helpful, please give it a round of applause 👏. Explore more Flutter-related content in my other posts.

For additional insights and updates, check out my LinkedIn profile. Thank you for your support!

Troubleshootings

TextField inside padding

contentPadding of InputDecoration

Cannot provide both a color and a decoration To provide both

specify color of BoxDecoration
remove color from Container

Suffix Icon Resizing

suffixIconConstraints

References

--

--

No responses yet