Skip to content

Commit bd44398

Browse files
[rfw] Add Flexible widget support to core widgets (#9750)
# [rfw] Add Flexible widget support This PR adds support for the `Flexible` widget to Remote Flutter Widgets (RFW), allowing developers to create flexible layouts with both loose and tight fitting behavior. Fixes [#173313](flutter/flutter#173313) The `Flexible` widget provides more granular control over flex layouts compared to the existing `Expanded` widget: - **FlexFit.loose**: Widget takes only the space it needs (default) - **FlexFit.tight**: Widget takes all available space (same as Expanded) **Implementation:** - Added `Flexible` widget to `lib/src/flutter/core_widgets.dart` - Supports `flex` (default: 1) and `fit` parameters (loose/tight, default: loose) - Uses existing `ArgumentDecoders.enumValue` pattern for FlexFit parsing - Maintains alphabetical ordering and follows existing RFW patterns - Added comprehensive test coverage in `test/core_widgets_test.dart` - Refactored tests based on code review feedback to reduce duplication **Usage Example:** ```rfwtxt import core.widgets; widget FlexibleDemo = Column( children: [ Flexible( flex: 1, fit: "loose", child: Text(text: "Takes needed space"), ), Flexible( flex: 2, fit: "tight", child: Text(text: "Takes all available space"), ), ], ); ``` **API Compatibility:** Follows Flutter's `Flexible` widget API exactly: - `flex: int` (default: 1) - `fit: FlexFit` (loose/tight, default: loose) - `child: Widget` (required) ## Pre-Review Checklist
1 parent cfe2fe2 commit bd44398

File tree

4 files changed

+203
-2
lines changed

4 files changed

+203
-2
lines changed

packages/rfw/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## NEXT
1+
## 1.1.0
22

3+
* Adds support for the `Flexible` core widget.
34
* Updates minimum supported SDK version to Flutter 3.35/Dart 3.9.
45

56
## 1.0.32

packages/rfw/lib/src/flutter/core_widgets.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import 'runtime.dart';
3434
/// * [DefaultTextStyle]
3535
/// * [Directionality]
3636
/// * [Expanded]
37+
/// * [Flexible]
3738
/// * [FittedBox]
3839
/// * [FractionallySizedBox]
3940
/// * [GestureDetector]
@@ -349,6 +350,14 @@ Map<String, LocalWidgetBuilder> get _coreWidgetsDefinitions => <String, LocalWid
349350
);
350351
},
351352

353+
'Flexible': (BuildContext context, DataSource source) {
354+
return Flexible(
355+
flex: source.v<int>(['flex']) ?? 1,
356+
fit: ArgumentDecoders.enumValue<FlexFit>(FlexFit.values, source, ['fit']) ?? FlexFit.loose,
357+
child: source.child(['child']),
358+
);
359+
},
360+
352361
'FittedBox': (BuildContext context, DataSource source) {
353362
return FittedBox(
354363
fit: ArgumentDecoders.enumValue<BoxFit>(BoxFit.values, source, ['fit']) ?? BoxFit.contain,

packages/rfw/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: rfw
22
description: "Remote Flutter widgets: a library for rendering declarative widget description files at runtime."
33
repository: https://github.com/flutter/packages/tree/main/packages/rfw
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+rfw%22
5-
version: 1.0.32
5+
version: 1.1.0
66

77
environment:
88
sdk: ^3.9.0

packages/rfw/test/core_widgets_test.dart

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,4 +300,195 @@ void main() {
300300
expect(renderClip.clipBehavior, equals(Clip.antiAlias));
301301
expect(renderClip.borderRadius, equals(BorderRadius.zero));
302302
});
303+
304+
testWidgets('Flexible widget with default values', (WidgetTester tester) async {
305+
final runtime = Runtime()
306+
..update(const LibraryName(<String>['core']), createCoreWidgets());
307+
addTearDown(runtime.dispose);
308+
final data = DynamicContent();
309+
310+
runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
311+
import core;
312+
widget root = Directionality(
313+
textDirection: "ltr",
314+
child: Column(
315+
children: [
316+
Flexible(
317+
child: Text(text: "Default flexible"),
318+
),
319+
],
320+
),
321+
);
322+
'''));
323+
324+
await tester.pumpWidget(
325+
RemoteWidget(
326+
runtime: runtime,
327+
data: data,
328+
widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
329+
),
330+
);
331+
await tester.pump();
332+
expect(find.byType(Flexible), findsOneWidget);
333+
final Flexible defaultFlexible = tester.widget<Flexible>(find.byType(Flexible));
334+
expect(defaultFlexible.flex, equals(1));
335+
expect(defaultFlexible.fit, equals(FlexFit.loose));
336+
});
337+
338+
testWidgets('Flexible widget with custom flex value', (WidgetTester tester) async {
339+
final runtime = Runtime()
340+
..update(const LibraryName(<String>['core']), createCoreWidgets());
341+
addTearDown(runtime.dispose);
342+
final data = DynamicContent();
343+
344+
runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
345+
import core;
346+
widget root = Directionality(
347+
textDirection: "ltr",
348+
child: Column(
349+
children: [
350+
Flexible(
351+
flex: 3,
352+
child: Text(text: "Custom flex"),
353+
),
354+
],
355+
),
356+
);
357+
'''));
358+
359+
await tester.pumpWidget(
360+
RemoteWidget(
361+
runtime: runtime,
362+
data: data,
363+
widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
364+
),
365+
);
366+
await tester.pump();
367+
expect(find.byType(Flexible), findsOneWidget);
368+
final Flexible customFlexFlexible = tester.widget<Flexible>(find.byType(Flexible));
369+
expect(customFlexFlexible.flex, equals(3));
370+
expect(customFlexFlexible.fit, equals(FlexFit.loose));
371+
});
372+
373+
testWidgets('Flexible widget with fit tight', (WidgetTester tester) async {
374+
final runtime = Runtime()
375+
..update(const LibraryName(<String>['core']), createCoreWidgets());
376+
addTearDown(runtime.dispose);
377+
final data = DynamicContent();
378+
379+
runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
380+
import core;
381+
widget root = Directionality(
382+
textDirection: "ltr",
383+
child: Column(
384+
children: [
385+
Flexible(
386+
flex: 2,
387+
fit: "tight",
388+
child: Text(text: "Tight fit"),
389+
),
390+
],
391+
),
392+
);
393+
'''));
394+
395+
await tester.pumpWidget(
396+
RemoteWidget(
397+
runtime: runtime,
398+
data: data,
399+
widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
400+
),
401+
);
402+
await tester.pump();
403+
expect(find.byType(Flexible), findsOneWidget);
404+
final Flexible tightFlexible = tester.widget<Flexible>(find.byType(Flexible));
405+
expect(tightFlexible.flex, equals(2));
406+
expect(tightFlexible.fit, equals(FlexFit.tight));
407+
});
408+
409+
testWidgets('Flexible widget with fit loose', (WidgetTester tester) async {
410+
final runtime = Runtime()
411+
..update(const LibraryName(<String>['core']), createCoreWidgets());
412+
addTearDown(runtime.dispose);
413+
final data = DynamicContent();
414+
415+
runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
416+
import core;
417+
widget root = Directionality(
418+
textDirection: "ltr",
419+
child: Column(
420+
children: [
421+
Flexible(
422+
flex: 4,
423+
fit: "loose",
424+
child: Text(text: "Loose fit"),
425+
),
426+
],
427+
),
428+
);
429+
'''));
430+
431+
await tester.pumpWidget(
432+
RemoteWidget(
433+
runtime: runtime,
434+
data: data,
435+
widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
436+
),
437+
);
438+
await tester.pump();
439+
expect(find.byType(Flexible), findsOneWidget);
440+
final Flexible looseFlexible = tester.widget<Flexible>(find.byType(Flexible));
441+
expect(looseFlexible.flex, equals(4));
442+
expect(looseFlexible.fit, equals(FlexFit.loose));
443+
});
444+
445+
testWidgets('Multiple Flexible widgets in Column', (WidgetTester tester) async {
446+
final runtime = Runtime()
447+
..update(const LibraryName(<String>['core']), createCoreWidgets());
448+
addTearDown(runtime.dispose);
449+
final data = DynamicContent();
450+
451+
runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
452+
import core;
453+
widget root = Directionality(
454+
textDirection: "ltr",
455+
child: Column(
456+
children: [
457+
Flexible(
458+
flex: 1,
459+
fit: "loose",
460+
child: Text(text: "First"),
461+
),
462+
Flexible(
463+
flex: 2,
464+
fit: "tight",
465+
child: Text(text: "Second"),
466+
),
467+
Flexible(
468+
flex: 1,
469+
child: Text(text: "Third"),
470+
),
471+
],
472+
),
473+
);
474+
'''));
475+
476+
await tester.pumpWidget(
477+
RemoteWidget(
478+
runtime: runtime,
479+
data: data,
480+
widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
481+
),
482+
);
483+
await tester.pump();
484+
expect(find.byType(Flexible), findsNWidgets(3));
485+
486+
final List<Flexible> flexibleWidgets = tester.widgetList<Flexible>(find.byType(Flexible)).toList();
487+
expect(flexibleWidgets[0].flex, equals(1));
488+
expect(flexibleWidgets[0].fit, equals(FlexFit.loose));
489+
expect(flexibleWidgets[1].flex, equals(2));
490+
expect(flexibleWidgets[1].fit, equals(FlexFit.tight));
491+
expect(flexibleWidgets[2].flex, equals(1));
492+
expect(flexibleWidgets[2].fit, equals(FlexFit.loose));
493+
});
303494
}

0 commit comments

Comments
 (0)