Implementation of Reply Messages in Mobile Chat
Reply mechanics is one of those features underestimated during planning. On the surface: display a quote above the input field, send parent_message_id to the server, render a preview in the feed. In practice: three platforms (iOS/Android/Flutter), different UI states, scrolling to the original message across 500+ positions, and edge cases like replying to a deleted message.
Data Schema and API
A message with reply stores parent_id (nullable FK to itself). The server returns either the full parent object (parent_message embedded) or just parent_id — then the client fetches it separately. The first variant is simpler for rendering but inflates the payload for long quotes. A compromise: return a truncated snapshot of the parent — { id, text_preview, sender_name, attachment_type } — without the full body.
Important: if a user replies to a message that is itself a reply, the UI displays only one level of nesting. Recursive quotes — confusion; WhatsApp and Telegram both settled on this long ago.
Implementation on iOS (UIKit / SwiftUI)
On UIKit the input field is a custom inputAccessoryView. When selecting reply, add a preview substrate above UITextView: a separate UIView with UILabel (sender name), UILabel (preview text, truncated to 80 characters via NSLineBreakMode.byTruncatingTail), UIButton for cancel. Appearance animation — changing inputAccessoryView.frame.size.height with UIView.animate(withDuration: 0.2), otherwise the keyboard jumps.
In the message cell, draw the reply block as a separate UIView above the bubble: left colored stripe via CALayer with backgroundColor, two labels. If the original message is deleted — show "Message deleted" in gray italic.
Scrolling to the original message — on tap of the reply block. If the message is in the current dataSource — collectionView.scrollToItem(at:, at: .centeredVertically, animated: true). If not (not all loaded) — request the page with the required message_id via API, load it, scroll. After scrolling, highlight the cell: change backgroundColor to .systemYellow.withAlphaComponent(0.3), remove after 1.2 seconds with UIView.animate.
SwiftUI — ScrollViewProxy.scrollTo(_:anchor:) in withAnimation. Simpler, but requires iOS 14+.
Implementation on Android (Jetpack Compose)
Reply preview above TextField — a separate composable that appears via AnimatedVisibility(visible = replyState != null, enter = slideInVertically + fadeIn). Close button clears replyState in ViewModel.
In LazyColumn each message checks parentMessage != null — if yes, before the bubble render ReplyPreview composable with vertical colored stripe via Box with Modifier.fillMaxHeight().width(3.dp).background(color).
Scrolling to original: LazyListState.animateScrollToItem(index). Find the index in snapshot via items.indexOfFirst { it.id == parentId }. If not found — trigger loading via PagingSource with initial key parentId.
Flutter
reply_state — in ChatCubit or ChangeNotifier. Preview above TextField — regular AnimatedContainer with Curve.easeOut. In ListView.builder / CustomScrollView with SliverList reply block — separate ReplyPreviewWidget inside Column with bubble.
Scrolling: if using flutter_chat_ui — there's a built-in callback onMessageTap, can add reply scroll via ItemScrollController from scrollable_positioned_list. Without third-party packages — ScrollController.animateTo with preliminary offset calculation by cell height (unstable with different sizes) or Scrollable.ensureVisible for a specific widget.
Workflow
API schema design and UI state planning → backend implementation (support parent_id, parent snapshot) → UI implementation on required platform → handling edge cases (deleted message, media quote, loading pagination) → testing on long threads.
Timeline
1-3 business days depending on platform (one or multiple) and API availability. Cost calculated individually after requirement analysis.







