arch-atlas

Create books button action

Presentation (Building the UI)

18:40

Summary

In this lesson, we construct the BooksAction widget for the book details screen. We create a reusable CustomButton using TextButton to retain Material splash effects, instead of relying on a raw Container. By wrapping two buttons in Expanded widgets inside a Row and adjusting their BorderRadii to match the outer corners, we achieve the cohesive pill-shaped UI element specified in the design.

Key Ideas

  • Building reusable CustomButton components in the core/widgets directory
  • Preferring TextButton over GestureDetector on a Container to retain native Material ripple effects
  • Passing selective BorderRadii to buttons placed side-by-side to create a combined UI shape
  • Using Expanded widgets inside a Row to evenly distribute available horizontal space between action buttons

Code Snippets

Reusable CustomButton Widgetdart
import 'package:flutter/material.dart';
import '../../../../core/utils/styles.dart';

class CustomButton extends StatelessWidget {
const CustomButton({
super.key,
required this.backgroundColor,
required this.textColor,
this.borderRadius,
required this.text,
this.fontSize,
});

final Color backgroundColor;
final Color textColor;
final BorderRadius? borderRadius;
final String text;
final double? fontSize;

@override
Widget build(BuildContext context) {
return SizedBox(
height: 48,
child: TextButton(
onPressed: () {},
style: TextButton.styleFrom(
backgroundColor: backgroundColor,
shape: RoundedRectangleBorder(
borderRadius: borderRadius ?? BorderRadius.circular(12),
),
),
child: Text(
text,
style: Styles.textStyle18.copyWith(
color: textColor,
fontWeight: FontWeight.w900,
fontSize: fontSize,
),
),
),
);
}
}
BooksAction Widgetdart
import 'package:flutter/material.dart';
import '../../../../core/widgets/custom_button.dart';

class BooksAction extends StatelessWidget {
const BooksAction({super.key});

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
children: const [
Expanded(
child: CustomButton(
text: '19.99€',
backgroundColor: Colors.white,
textColor: Colors.black,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
bottomLeft: Radius.circular(12),
),
),
),
Expanded(
child: CustomButton(
fontSize: 16,
text: 'Free preview',
backgroundColor: Color(0xffEF8262),
textColor: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(12),
bottomRight: Radius.circular(12),
),
),
),
],
),
);
}
}

Architecture Insight

Architecture insight

CustomButton in core/widgets/ is a textbook CA-correct primitive: it accepts text and styling props, not a `Book`.

The rule: a widget in core/ must never know about feature-specific entities. The moment you see CustomButton({required Book book}), it has stopped being a generic primitive — move it back into the feature that owns the Book concept.

My Notes

Creating a Reusable Button

The action buttons at the bottom of the book details screen are a common UI pattern. We create a CustomButton widget in core/widgets because this kind of button usually appears across multiple views in an app.

Container vs. TextButton

A common mistake is building buttons with a Container and a GestureDetector. While this works for the layout, it lacks the built-in Material splash/ripple effect that signals a touch event to the user. We instead use a TextButton, applying TextButton.styleFrom to easily customize the backgroundColor and shape.

Sizing and Corners

  • We wrap the TextButton in a SizedBox(height: 48) to fix its vertical size per the design.
  • The borderRadius property is nullable. If omitted, we fallback to BorderRadius.circular(12).
  • By passing BorderRadius.only(...) from the parent, we can square off specific corners. In BooksAction, the left button rounds only its left corners, and the right button rounds only its right corners, creating a seamless pill look.

[Editor's note] Keep your UI elements as flexible as possible. By exposing fontSize and borderRadius as optional parameters, CustomButton is ready to be reused in dialogs, forms, or anywhere else without adding unnecessary boolean flags or complex logic inside the widget.