This episode enters advanced territory: custom render objects and widgets beneath the surface, platform-specific adaptation and responsive apps, micro frontends and plugin-driven architectures, and hybrid app patterns with web and desktop.

You're already comfortable at the widget layer. Episode 18 goes deeper and widens the perspective: understanding custom render objects for total rendering control, adapting to platform differences, organizing large apps with modular architecture, and building hybrid patterns for web and desktop.
An ordinary widget describes configuration; a render object does the real work — layout and painting. When a layout need can't be met by built-in widgets, you write your own render object.
Render objects are usually accessed through SingleChildRenderObjectWidget, which creates and updates the render object:
class StretchWidget extends SingleChildRenderObjectWidget {
const StretchWidget({super.key, required super.child});
@override
RenderObject createRenderObject(BuildContext context) {
return _StretchRenderObject();
}
@override
void updateRenderObject(
BuildContext context,
covariant _StretchRenderObject renderObject,
) {}
}createRenderObject builds the render object; updateRenderObject syncs the configuration when the widget is updated. Layout customization then lives in the _StretchRenderObject class, which extends RenderBox.
Write a render object only when widget composition isn't enough: layout that depends on a child's size in a non-standard way, or performance that can't be reached with ordinary widgets. For 99 percent of cases, a combination of LayoutBuilder, CustomSingleChildLayout, and CustomPaint is sufficient — don't over-optimize.
An app that runs on phones, tablets, and desktops needs adaptive layout:
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 900) {
return const WideLayout();
} else if (constraints.maxWidth >= 600) {
return const MediumLayout();
}
return const NarrowLayout();
},
)constraints.maxWidth determines the available width, then the layout is chosen by breakpoint. LayoutBuilder is more responsive than MediaQuery because it follows the widget's constraints, not the full screen size.
To meet each platform's conventions, adjust the navigation pattern:
if (constraints.maxWidth >= 900) {
return const NavigationRail(
selectedIndex: 0,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Beranda'),
),
],
);
}NavigationRail replaces BottomNavigationBar on wide screens — the standard pattern for desktop apps. Small adaptations like this make the app feel "native" on every platform.
For enterprise apps, apply feature modules (from episode 11) with strict rules: each module has a public interface and clear dependency injection. This is analogous to microservices on the backend — every team can work on its module without conflicts.
Make capabilities extensible through interfaces:
abstract class PaymentProvider {
Future<PaymentResult> process(PaymentRequest request);
}
class MidtransProvider implements PaymentProvider {
@override
Future<PaymentResult> process(PaymentRequest request) async {
return PaymentResult(success: true);
}
}PaymentProvider is an interface, and MidtransProvider is one implementation. Adding a new payment gateway just means writing a new class without touching the core flow — this is the strategy pattern that keeps large apps growable.
Flutter already handles mobile, web, and desktop from one codebase. Target differences are managed through:
universal_html package for browser APIs on the web target.-p flag during flutter create.flutter create . --platforms=android,ios,web,linux,macos,windowsflutter create . --platforms=android,ios,web,linux,macos,windows adds the missing platform folders to an existing project. One codebase now covers every target.
Separate truly platform-specific logic into directories with platform suffixes and conditional imports. Keep behavior that can be unified in one place. The principle: unify as much as possible, separate only where the technology truly differs.
Key takeaways:
SingleChildRenderObjectWidget.LayoutBuilder with breakpoints produces adaptive layouts.NavigationRail for wide screens.flutter create --platforms.In the next episode 19 we discuss operational readiness and runbooks — runbooks for app crashes and release issues, monitoring app stability with analytics and error reporting, rollout and rollback strategies, and team workflows for maintenance. Your app is ready to be maintained in production.