Skip to content

Commit a349531

Browse files
committed
SDK Queuing System #1
1 parent c36eb37 commit a349531

File tree

8 files changed

+204
-100
lines changed

8 files changed

+204
-100
lines changed

example/lib/main.dart

+15-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ void main() async {
1111
config: CodelesslyConfig(
1212
authToken: "cSlMIT93cj1lXkBuYk5QNmImSTUsTHw2MDQ4VkdlOCZFLHc7",
1313
isPreview: kDebugMode,
14-
preload: false,
14+
preload: true,
1515
),
1616
);
1717

@@ -34,7 +34,18 @@ class _MyAppState extends State<MyApp> {
3434
showPanel: true,
3535
wrapperBuilder: (context, child) {
3636
return MaterialApp(
37-
home: child,
37+
home: Column(
38+
children: [
39+
ElevatedButton.icon(
40+
onPressed: () {
41+
Codelessly.instance.reset(clearCache: true);
42+
},
43+
icon: const Icon(Icons.refresh),
44+
label: const Text('Reset'),
45+
),
46+
if (child != null) child,
47+
],
48+
),
3849
);
3950
},
4051
stories: [
@@ -47,7 +58,8 @@ class _MyAppState extends State<MyApp> {
4758
return const CupertinoActivityIndicator();
4859
},
4960
);
50-
}),
61+
},
62+
),
5163
Story(
5264
name: 'License UI',
5365
builder: (context) {

example/macos/Runner.xcodeproj/project.pbxproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@
202202
isa = PBXProject;
203203
attributes = {
204204
LastSwiftUpdateCheck = 0920;
205-
LastUpgradeCheck = 1300;
205+
LastUpgradeCheck = 1430;
206206
ORGANIZATIONNAME = "";
207207
TargetAttributes = {
208208
33CC10EC2044A3C60003C045 = {

example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1300"
3+
LastUpgradeVersion = "1430"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

lib/src/codelessly.dart

+26-19
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,16 @@ class Codelessly {
9595
/// when they are triggered.
9696
final Map<String, CodelesslyFunction> functions = {};
9797

98-
CodelesslyStatus _status = CodelesslyStatus.empty;
98+
CStatus _status = CStatus.empty();
9999

100100
/// Returns the current status of this SDK instance.
101-
CodelesslyStatus get status => _status;
101+
CStatus get status => _status;
102102

103-
final StreamController<CodelesslyStatus> _statusStreamController =
104-
StreamController.broadcast()..add(CodelesslyStatus.empty);
103+
final StreamController<CStatus> _statusStreamController =
104+
StreamController.broadcast()..add(CStatus.empty());
105105

106106
/// Returns a stream of status updates for this SDK instance.
107-
Stream<CodelesslyStatus> get statusStream => _statusStreamController.stream;
107+
Stream<CStatus> get statusStream => _statusStreamController.stream;
108108

109109
LocalStorage get localStorage => dataManager.localStorage;
110110

@@ -139,7 +139,7 @@ class Codelessly {
139139

140140
// If the config is not null, update the status to configured.
141141
if (_config != null) {
142-
_updateStatus(CodelesslyStatus.configured);
142+
_updateStatus(CStatus.configured());
143143
}
144144
}
145145

@@ -150,7 +150,7 @@ class Codelessly {
150150
if (completeDispose) {
151151
_statusStreamController.close();
152152
} else {
153-
_status = CodelesslyStatus.empty;
153+
_status = CStatus.empty();
154154
_statusStreamController.add(_status);
155155
}
156156

@@ -178,8 +178,8 @@ class Codelessly {
178178
_templateDataManager?.invalidate('Template');
179179
_authManager?.invalidate();
180180

181-
_config = null;
182-
_status = CodelesslyStatus.empty;
181+
_status =
182+
config == null ? CStatus.empty() : CStatus.configured();
183183
_statusStreamController.add(_status);
184184

185185
if (clearCache) {
@@ -207,7 +207,7 @@ class Codelessly {
207207

208208
/// Internally updates the status of this instance of the SDK and emits a
209209
/// status update event to the [statusStream].
210-
void _updateStatus(CodelesslyStatus status) {
210+
void _updateStatus(CStatus status) {
211211
if (_status == status) {
212212
return;
213213
}
@@ -228,7 +228,7 @@ class Codelessly {
228228
/// [Codelessly] SDK is the global instance rather than a local one, it will
229229
/// configure and initialize the SDK automatically via its widget's
230230
/// constructor parameters.
231-
CodelesslyStatus configure({
231+
CStatus configure({
232232
CodelesslyConfig? config,
233233

234234
// Optional data and functions.
@@ -249,7 +249,7 @@ class Codelessly {
249249
);
250250

251251
assert(
252-
status == CodelesslyStatus.empty,
252+
status == CStatus.empty(),
253253
'The SDK cannot be configured if it is not idle. '
254254
'Consider calling [Codelessly.dispose] before reconfiguring.',
255255
);
@@ -277,7 +277,7 @@ class Codelessly {
277277
if (functions != null) {
278278
this.functions.addAll(functions);
279279
}
280-
_updateStatus(CodelesslyStatus.configured);
280+
_updateStatus(CStatus.configured());
281281
return status;
282282
}
283283

@@ -315,7 +315,7 @@ class Codelessly {
315315
if (exception.layoutID != null) {
316316
return;
317317
}
318-
_updateStatus(CodelesslyStatus.error);
318+
_updateStatus(CStatus.error());
319319
},
320320
);
321321

@@ -332,7 +332,7 @@ class Codelessly {
332332
/// [Codelessly] SDK is the global instance rather than a local one, it will
333333
/// configure and/or initialize the SDK automatically via its widget's
334334
/// constructor parameters, if specified.
335-
Future<CodelesslyStatus> initialize({
335+
Future<CStatus> initialize({
336336
CodelesslyConfig? config,
337337

338338
// Optional data and functions.
@@ -368,7 +368,7 @@ class Codelessly {
368368
automaticallySendCrashReports: _config!.automaticallySendCrashReports,
369369
);
370370
try {
371-
_updateStatus(CodelesslyStatus.configured);
371+
_updateStatus(CStatus.configured());
372372

373373
// Clean up.
374374
if (cacheManager != null) _cacheManager?.dispose();
@@ -441,12 +441,14 @@ class Codelessly {
441441
LocalDataRepository(cacheManager: this.cacheManager),
442442
);
443443

444-
_updateStatus(CodelesslyStatus.loading);
444+
_updateStatus(CStatus.loading('initialized_managers'));
445445

446446
log('[SDK] [INIT] Initializing cache manager');
447447
// The cache manager initializes first to load the local cache.
448448
await this.cacheManager.init();
449449

450+
_updateStatus(CStatus.loading('initialized_cache'));
451+
450452
// The auth manager initializes second to look up cached auth data
451453
// from the cache manager. If no auth data is available, it halts the
452454
// entire process and awaits to authenticate with the server.
@@ -461,6 +463,8 @@ class Codelessly {
461463
await this.authManager.init();
462464
_config!.publishSource =
463465
this.authManager.getBestPublishSource(_config!);
466+
467+
_updateStatus(CStatus.loading('initialized_auth'));
464468
} else {
465469
log('[SDK] [INIT] A slug was provided. Acutely skipping authentication.');
466470
}
@@ -497,6 +501,8 @@ class Codelessly {
497501
}
498502
break;
499503
}
504+
505+
_updateStatus(CStatus.loading('initialized_data_managers'));
500506
} else {
501507
if (!initializeDataManagers) {
502508
log(
@@ -523,13 +529,14 @@ class Codelessly {
523529
this.authManager.authData!.projectId,
524530
);
525531
});
532+
_updateStatus(CStatus.loading('initialized_slug'));
526533
}
527534

528535
log('[SDK] [INIT] Codelessly ${_instance == this ? 'global' : 'local'} instance initialization complete.');
529536

530-
_updateStatus(CodelesslyStatus.loaded);
537+
_updateStatus(CStatus.loaded());
531538
} catch (error, stacktrace) {
532-
_updateStatus(CodelesslyStatus.error);
539+
_updateStatus(CStatus.error());
533540
CodelesslyErrorHandler.instance.captureException(
534541
error,
535542
stacktrace: stacktrace,

lib/src/codelessly_config.dart

+32
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,35 @@ enum CodelesslyStatus {
132132
/// The SDK has an error..
133133
error,
134134
}
135+
136+
sealed class CStatus {
137+
const CStatus();
138+
139+
factory CStatus.empty() => const CEmpty();
140+
factory CStatus.configured() => const CConfigured();
141+
factory CStatus.loading(String step) => CLoading(step);
142+
factory CStatus.loaded() => const CLoaded();
143+
factory CStatus.error() => const CError();
144+
}
145+
146+
class CEmpty extends CStatus {
147+
const CEmpty();
148+
}
149+
150+
class CConfigured extends CStatus {
151+
const CConfigured();
152+
}
153+
154+
class CLoading extends CStatus {
155+
final String step;
156+
157+
const CLoading(this.step);
158+
}
159+
160+
class CLoaded extends CStatus {
161+
const CLoaded();
162+
}
163+
164+
class CError extends CStatus {
165+
const CError();
166+
}

lib/src/data/data_manager.dart

+58-29
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ class DataManager {
1717
/// not need to be initialized again.
1818
bool initialized = false;
1919

20+
/// This is set to true once the while loop that processes the
21+
/// [_downloadQueue] finishes processing in the initialization of this
22+
/// [DataManager].
23+
///
24+
/// Queuing is ignored after this is set to true.
25+
bool queuingDone = false;
26+
2027
/// The passed config from the SDK.
2128
final CodelesslyConfig config;
2229

@@ -63,6 +70,16 @@ class DataManager {
6370
/// The slug for the project as defined in the editor's publish settings.
6471
late String? slug = config.slug;
6572

73+
/// The download queue holds the list of layout IDs that need to be
74+
/// downloaded in order. Tracking this as an external queue allows
75+
/// us to interrupt and inject different layouts to prioritize when needed.
76+
///
77+
/// Use Case: Config preloading is set to true, but a CodelesslyWidget got
78+
/// rendered on the screen with a layoutID specified before preload could
79+
/// finish its queue.
80+
/// We inject the layoutID at the start of this queue to prioritize it.
81+
final List<String> _downloadQueue = [];
82+
6683
/// Creates a new instance of [DataManager] with the given [config].
6784
DataManager({
6885
required this.config,
@@ -269,34 +286,20 @@ class DataManager {
269286
// If a [layoutID] is not specified, then we need to download all layouts
270287
// in the background.
271288
if (config.preload) {
272-
final preloadFuture = Future(() async {
273-
log(
274-
'[DataManager] Config preload was specified during init. Downloading ${_publishModel!.updates.layouts.length} layouts...',
275-
);
276-
for (final String layoutID in _publishModel!.updates.layouts.keys) {
277-
log(
278-
'[DataManager] \tDownloading layout [$layoutID]...',
279-
);
280-
await getOrFetchPopulatedLayout(layoutID: layoutID);
281-
log(
282-
'[DataManager] \tLayout [$layoutID] during init download complete.',
283-
);
284-
}
289+
log('[DataManager] Config preload was specified during init, but a layoutID was not specified. Waiting for all layouts to download...');
290+
log('[DataManager] Config preload was specified during init. Downloading ${_publishModel!.updates.layouts.length} layouts as a queue...');
291+
_downloadQueue.addAll(_publishModel!.updates.layouts.keys);
292+
293+
while (_downloadQueue.isNotEmpty) {
294+
final String layoutID = _downloadQueue.removeAt(0);
295+
log('[DataManager] \tDownloading layout [$layoutID]...');
296+
await getOrFetchPopulatedLayout(layoutID: layoutID);
297+
log('[DataManager] \tLayout [$layoutID] during init download complete.');
298+
}
285299

286-
log(
287-
'[DataManager] All layouts during init download complete.',
288-
);
289-
});
300+
queuingDone = true;
290301

291-
// Don't await for all the of the layouts to download if the data manager
292-
// is initialized with a layoutID. The layoutID should be prioritized
293-
// and downloaded first, the rest can be downloaded in the background.
294-
if (layoutID == null) {
295-
log(
296-
'[DataManager] Config preload was specified during init, but a layoutID was not specified. Waiting for all layouts to download...',
297-
);
298-
await preloadFuture;
299-
}
302+
log('[DataManager] All layouts during init download complete.');
300303
}
301304
}
302305

@@ -835,10 +838,36 @@ class DataManager {
835838
return conditionUpdates;
836839
}
837840

838-
/// [layoutID] is the identifier of the layout to be fetched or retrieved.
841+
/// Similar to [getOrFetchPopulatedLayout], but utilizes the download queue
842+
/// to respect the order of downloads.
843+
Future<void> queueLayout({
844+
required String layoutID,
845+
bool prioritize = false,
846+
}) async {
847+
if (_publishModel != null && queuingDone) {
848+
log('[DataManager] [queueLayout] Download queue is empty. Downloading layout [$layoutID] immediately...');
849+
await getOrFetchPopulatedLayout(layoutID: layoutID);
850+
log('[DataManager] [queueLayout] Layout [$layoutID] download complete.');
851+
} else {
852+
if (_downloadQueue.contains(layoutID)) {
853+
log('[DataManager] [queueLayout] Layout [$layoutID] is already in the queue. Skipping.');
854+
return;
855+
}
856+
857+
if (prioritize) {
858+
log('[DataManager] [queueLayout] Prioritizing this layout. Inserting [$layoutID] to the front of the queue.');
859+
_downloadQueue.insert(0, layoutID);
860+
} else {
861+
log('[DataManager] [queueLayout] Adding [$layoutID] to the back of the queue.');
862+
_downloadQueue.add(layoutID);
863+
}
864+
}
865+
}
866+
867+
/// Downloads or looks up the requested [layoutID] along with its associated
868+
/// fonts, and emits the updated [_publishModel].
839869
///
840-
/// Fetches or gets the requested [layoutID] along with its associated fonts,
841-
/// and emits the updated [_publishModel].
870+
/// [layoutID] is the identifier of the layout to be fetched or retrieved.
842871
///
843872
/// This method first checks if the layout with the given [layoutID] is
844873
/// already cached.

0 commit comments

Comments
 (0)