Skip to content

Commit 0b3a616

Browse files
committed
feat: add DynamicDataProvider and TemplateBuilder
- Add shared template utilities for nested data extraction and placeholder processing - Introduce DynamicDataProvider model/parser and DynamicDataScope for scoped fetched data - Introduce TemplateBuilder model/parser for data-driven child generation from lists or direct JSON - Wire DynamicDataProvider/TemplateBuilder into StacService resolution and parser registry - Refactor movie_app home_screen to use DynamicDataProvider + TemplateBuilder instead of raw JSON itemTemplate
1 parent 6d090ea commit 0b3a616

15 files changed

Lines changed: 901 additions & 360 deletions

File tree

examples/movie_app/stac/home_screen.dart

Lines changed: 90 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -19,165 +19,40 @@ StacWidget homeScreen() {
1919
method: Method.get,
2020
),
2121
),
22-
StacColumn(
23-
children: [
24-
StacPadding(
25-
padding: StacEdgeInsets.only(
26-
left: 16,
27-
right: 16,
28-
top: 24,
29-
bottom: 10,
30-
),
31-
child: StacRow(
32-
mainAxisAlignment: StacMainAxisAlignment.spaceBetween,
33-
children: [
34-
StacText(
35-
data: AppStrings.nowPlaying,
36-
style: StacThemeData.textTheme.labelLarge,
37-
),
38-
],
39-
),
40-
),
41-
StacSizedBox(
42-
height: 164,
43-
child: StacDynamicView(
44-
request: StacNetworkRequest(
45-
url: AppApi.getNowPlayingMoviesUrl(),
46-
method: Method.get,
47-
),
48-
targetPath: 'results',
49-
template: _buildMovieListViewTemplate(),
50-
),
51-
),
52-
],
22+
_buildMovieSection(
23+
title: AppStrings.nowPlaying,
24+
request: StacNetworkRequest(
25+
url: AppApi.getNowPlayingMoviesUrl(),
26+
method: Method.get,
27+
),
5328
),
54-
StacColumn(
55-
children: [
56-
StacPadding(
57-
padding: StacEdgeInsets.only(
58-
left: 16,
59-
right: 16,
60-
top: 24,
61-
bottom: 10,
62-
),
63-
child: StacRow(
64-
mainAxisAlignment: StacMainAxisAlignment.spaceBetween,
65-
children: [
66-
StacText(
67-
data: AppStrings.popularMovies,
68-
style: StacThemeData.textTheme.labelLarge,
69-
),
70-
],
71-
),
72-
),
73-
StacSizedBox(
74-
height: 164,
75-
child: StacDynamicView(
76-
request: StacNetworkRequest(
77-
url: AppApi.getPopularMoviesUrl(),
78-
method: Method.get,
79-
),
80-
targetPath: 'results',
81-
template: _buildMovieListViewTemplate(),
82-
),
83-
),
84-
],
29+
_buildMovieSection(
30+
title: AppStrings.popularMovies,
31+
request: StacNetworkRequest(
32+
url: AppApi.getPopularMoviesUrl(),
33+
method: Method.get,
34+
),
8535
),
86-
StacColumn(
87-
children: [
88-
StacPadding(
89-
padding: StacEdgeInsets.only(
90-
left: 16,
91-
right: 16,
92-
top: 24,
93-
bottom: 10,
94-
),
95-
child: StacRow(
96-
mainAxisAlignment: StacMainAxisAlignment.spaceBetween,
97-
children: [
98-
StacText(
99-
data: AppStrings.trendingMovies,
100-
style: StacThemeData.textTheme.labelLarge,
101-
),
102-
],
103-
),
104-
),
105-
StacSizedBox(
106-
height: 164,
107-
child: StacDynamicView(
108-
request: StacNetworkRequest(
109-
url: AppApi.getTrendingMoviesUrl(),
110-
method: Method.get,
111-
),
112-
targetPath: 'results',
113-
template: _buildMovieListViewTemplate(),
114-
),
115-
),
116-
],
36+
_buildMovieSection(
37+
title: AppStrings.trendingMovies,
38+
request: StacNetworkRequest(
39+
url: AppApi.getTrendingMoviesUrl(),
40+
method: Method.get,
41+
),
11742
),
118-
StacColumn(
119-
children: [
120-
StacPadding(
121-
padding: StacEdgeInsets.only(
122-
left: 16,
123-
right: 16,
124-
top: 24,
125-
bottom: 10,
126-
),
127-
child: StacRow(
128-
mainAxisAlignment: StacMainAxisAlignment.spaceBetween,
129-
children: [
130-
StacText(
131-
data: AppStrings.topRated,
132-
style: StacThemeData.textTheme.labelLarge,
133-
),
134-
],
135-
),
136-
),
137-
StacSizedBox(
138-
height: 164,
139-
child: StacDynamicView(
140-
request: StacNetworkRequest(
141-
url: AppApi.getTopRatedMoviesUrl(),
142-
method: Method.get,
143-
),
144-
targetPath: 'results',
145-
template: _buildMovieListViewTemplate(),
146-
),
147-
),
148-
],
43+
_buildMovieSection(
44+
title: AppStrings.topRated,
45+
request: StacNetworkRequest(
46+
url: AppApi.getTopRatedMoviesUrl(),
47+
method: Method.get,
48+
),
14949
),
150-
StacColumn(
151-
children: [
152-
StacPadding(
153-
padding: StacEdgeInsets.only(
154-
left: 16,
155-
right: 16,
156-
top: 24,
157-
bottom: 10,
158-
),
159-
child: StacRow(
160-
mainAxisAlignment: StacMainAxisAlignment.spaceBetween,
161-
children: [
162-
StacText(
163-
data: AppStrings.upcomingMovies,
164-
style: StacThemeData.textTheme.labelLarge,
165-
),
166-
],
167-
),
168-
),
169-
StacSizedBox(
170-
height: 164,
171-
child: StacDynamicView(
172-
request: StacNetworkRequest(
173-
url: AppApi.getUpcomingMoviesUrl(),
174-
method: Method.get,
175-
),
176-
targetPath: 'results',
177-
template: _buildMovieListViewTemplate(),
178-
),
179-
),
180-
],
50+
_buildMovieSection(
51+
title: AppStrings.upcomingMovies,
52+
request: StacNetworkRequest(
53+
url: AppApi.getUpcomingMoviesUrl(),
54+
method: Method.get,
55+
),
18156
),
18257
StacSizedBox(height: 80),
18358
],
@@ -206,36 +81,67 @@ StacWidget homeScreen() {
20681
);
20782
}
20883

209-
/// Helper function to build a ListView template with itemTemplate for movie lists.
210-
/// Note: itemTemplate is a parser-specific feature handled by the dynamicView parser.
211-
/// We construct the template as JSON to include itemTemplate.
212-
StacWidget _buildMovieListViewTemplate() {
213-
// Create template JSON with itemTemplate (parser-specific feature)
214-
final templateJson = {
215-
'type': 'listView',
216-
'scrollDirection': 'horizontal',
217-
'shrinkWrap': true,
218-
'separator': StacSizedBox(width: 8).toJson(),
219-
'padding': StacEdgeInsets.only(left: 16).toJson(),
220-
'itemTemplate': StacGestureDetector(
221-
onTap: StacSetValueAction(
222-
values: [
223-
{'key': 'movie_id', 'value': '{{id}}'},
224-
],
225-
action: StacNavigator.pushStac('detail_screen'),
226-
),
227-
child: StacClipRRect(
228-
borderRadius: StacBorderRadius.all(6),
229-
child: StacImage(
230-
imageType: StacImageType.network,
231-
src: '${AppApi.imageBaseUrl}/{{poster_path}}',
232-
width: 108,
84+
StacWidget _buildMovieSection({
85+
required String title,
86+
required StacNetworkRequest request,
87+
}) {
88+
return StacDynamicDataProvider(
89+
id: title,
90+
request: request,
91+
targetPath: 'results',
92+
child: StacColumn(
93+
children: [
94+
StacPadding(
95+
padding: StacEdgeInsets.only(
96+
left: 16,
97+
right: 16,
98+
top: 24,
99+
bottom: 10,
100+
),
101+
child: StacRow(
102+
mainAxisAlignment: StacMainAxisAlignment.spaceBetween,
103+
children: [
104+
StacText(
105+
data: title,
106+
style: StacThemeData.textTheme.labelLarge,
107+
),
108+
],
109+
),
110+
),
111+
StacSizedBox(
233112
height: 164,
113+
child: StacTemplateBuilder(
114+
providerId: title,
115+
itemTemplate: _buildMoviePosterItem(),
116+
child: StacListView(
117+
scrollDirection: StacAxis.horizontal,
118+
shrinkWrap: true,
119+
separator: StacSizedBox(width: 8),
120+
padding: StacEdgeInsets.only(left: 16),
121+
),
122+
),
234123
),
235-
),
236-
).toJson(),
237-
};
124+
],
125+
),
126+
);
127+
}
238128

239-
// Create a StacWidget with the JSON data
240-
return StacWidget(jsonData: templateJson);
129+
StacWidget _buildMoviePosterItem() {
130+
return StacGestureDetector(
131+
onTap: StacSetValueAction(
132+
values: [
133+
{'key': 'movie_id', 'value': '{{id}}'},
134+
],
135+
action: StacNavigator.pushStac('detail_screen'),
136+
),
137+
child: StacClipRRect(
138+
borderRadius: StacBorderRadius.all(6),
139+
child: StacImage(
140+
imageType: StacImageType.network,
141+
src: '${AppApi.imageBaseUrl}/{{poster_path}}',
142+
width: 108,
143+
height: 164,
144+
),
145+
),
146+
);
241147
}

examples/stac_gallery/pubspec.lock

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,9 +861,18 @@ packages:
861861
description:
862862
name: test_api
863863
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
864+
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
864865
url: "https://pub.dev"
865866
source: hosted
866867
version: "0.7.9"
868+
timing:
869+
dependency: transitive
870+
description:
871+
name: timing
872+
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
873+
url: "https://pub.dev"
874+
source: hosted
875+
version: "1.0.2"
867876
typed_data:
868877
dependency: transitive
869878
description:

packages/stac/lib/src/framework/stac_service.dart

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import 'package:stac/src/parsers/widgets/stac_row/stac_row_parser.dart';
1919
import 'package:stac/src/parsers/widgets/stac_text/stac_text_parser.dart';
2020
import 'package:stac/src/parsers/widgets/stac_tool_tip/stac_tool_tip_parser.dart';
2121
import 'package:stac/src/services/stac_network_service.dart';
22+
import 'package:stac/src/utils/template_utils.dart';
2223
import 'package:stac/src/utils/variable_resolver.dart';
2324
import 'package:stac_core/core/stac_options.dart';
2425
import 'package:stac_core/stac_core.dart';
@@ -128,6 +129,7 @@ class StacService {
128129
const StacAspectRatioParser(),
129130
const StacFittedBoxParser(),
130131
const StacLimitedBoxParser(),
132+
const StacDynamicDataProviderParser(),
131133
const StacDynamicViewParser(),
132134
const StacDropdownMenuParser(),
133135
const StacClipRRectParser(),
@@ -140,6 +142,7 @@ class StacService {
140142
const StacBackdropFilterParser(),
141143
const StacVerticalDividerParser(),
142144
const StacSelectableTextParser(),
145+
const StacTemplateBuilderParser(),
143146
];
144147

145148
static final _actionParsers = <StacActionParser>[
@@ -236,7 +239,12 @@ class StacService {
236239
? json
237240
: resolveVariablesInJson(json, StacRegistry.instance);
238241

239-
final model = stacParser.getModel(resolvedJson);
242+
// Second pass: resolve {{providerId.path}} from DynamicDataScope
243+
final fullyResolved = widgetType == WidgetType.setValue.name
244+
? resolvedJson
245+
: resolveDynamicDataInJson(resolvedJson, context);
246+
247+
final model = stacParser.getModel(fullyResolved);
240248
return stacParser.parse(context, model);
241249
} catch (e, stackTrace) {
242250
// Log error with full context
@@ -293,7 +301,12 @@ class StacService {
293301
? widget.toJson()
294302
: resolveVariablesInJson(widget.toJson(), StacRegistry.instance);
295303

296-
final model = stacParser.getModel(resolvedJson);
304+
// Second pass: resolve {{providerId.path}} from DynamicDataScope
305+
final fullyResolved = widgetType == WidgetType.setValue.name
306+
? resolvedJson
307+
: resolveDynamicDataInJson(resolvedJson, context);
308+
309+
final model = stacParser.getModel(fullyResolved);
297310
return stacParser.parse(context, model);
298311
} catch (e, stackTrace) {
299312
_logError(

0 commit comments

Comments
 (0)