Implementing KakaoTalk Chat Input Box in Flutter
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