init method
Initializes the instance.
If a layoutID is specified, that layout will be downloaded from the
server first, then the rest of the layouts will be streamed passively
in the background.
If a layoutID is null, all layouts will be downloaded immediately and
awaited.
Implementation
Future<void> init({required String? layoutID}) async {
DebugLogger.instance.printFunction('init(layoutID: $layoutID)', name: name);
assert(
layoutID != null || config.preload || config.slug != null,
'If [layoutID] is null, [config.preload] must be true. If both are not '
'specified, then a slug must be provided in the config.',
);
final Stopwatch stopwatch = Stopwatch()..start();
status = CStatus.loading(CLoadingState.initializedDataManagers);
queuingDone = false;
// Initialize all locally cached data.
final cachedModel = localDataRepository.fetchPublishModel(
source: config.publishSource,
);
_publishModel ??= cachedModel;
if (_publishModel != null) {
DebugLogger.instance.printInfo(
'Publish model is cached locally. Emitting.',
name: name,
);
await emitPublishModel();
loadFontsFromPublishModel();
}
// A slug was specified. We need a layout FAST.
if (config.slug case String slug) {
DebugLogger.instance.printInfo(
'[slug] Slug was specified [$slug]. Going through slug flow...',
name: name,
);
final Stopwatch bundleStopWatch = Stopwatch()..start();
try {
DebugLogger.instance.printInfo(
'[slug] Downloading complete publish bundle for slug $slug.',
name: name,
);
if (_publishModel == null) {
DebugLogger.instance.printInfo(
'[slug] Publish model is not cached locally. Downloading complete publish bundle for slug $slug in foreground.',
name: name,
);
} else {
DebugLogger.instance.printInfo(
'[slug] Publish model is already cached locally. Downloading complete publish bundle for slug $slug in background.',
name: name,
);
}
final publishBundleFuture =
fetchCompletePublishBundle(
slug: slug,
source: config.publishSource,
).then((success) {
if (success) {
DebugLogger.instance.printInfo(
'[slug] Complete publish model from slug is downloaded in background. Emitting.',
name: name,
);
loadFontsFromPublishModel();
} else {
errorLogger.captureException(
'Failed to download complete publish bundle',
message:
'Failed to download complete publish bundle for slug $slug.',
type: 'bundle_download_failed',
);
}
});
if (_publishModel == null) {
await publishBundleFuture;
// Nothing cached and nothing downloaded: there is no app to show.
// Fail, so the widget shows an error instead of loading forever.
if (_publishModel == null) {
throw Exception('Could not download the published app "$slug".');
}
} else {
// Background refresh: report failures instead of leaving them as
// unhandled async errors.
unawaited(
publishBundleFuture.catchError((Object e, StackTrace st) {
errorLogger.captureException(
e,
message:
'Failed to refresh publish bundle for slug $slug in background.',
type: 'bundle_download_failed',
stackTrace: st,
);
}),
);
}
_logTime(stopwatch);
} catch (e, stackTrace) {
DebugLogger.instance.log(
'[slug] Failed to download complete publish bundle for slug $slug.\nError: $e',
category: DebugCategory.error,
name: name,
level: Level.WARNING,
);
DebugLogger.instance.printInfo(
'[slug] Since no publish model is cached, this is a complete stop to the data manager.',
name: name,
);
_logTime(stopwatch);
if (e is Exception) {
ErrorLogger.instance.captureException(
e,
message:
'Failed to download complete publish bundle for slug $slug.',
type: 'bundle_download_failed',
stackTrace: stackTrace,
);
rethrow;
}
} finally {
bundleStopWatch.stop();
DebugLogger.instance.printInfo(
'[slug] Publish bundle flow took ${bundleStopWatch.elapsedMilliseconds}ms or ${bundleStopWatch.elapsed.inSeconds}s.',
name: name,
);
}
} else {
DebugLogger.instance.printInfo(
'[slug] Slug is ${config.slug}. Skipping slug flow.',
name: name,
);
}
if (authManager.authData == null) {
DebugLogger.instance.printInfo(
'No auth data is available. Continuing as if offline.',
name: name,
);
_logTime(stopwatch);
return;
}
final AuthData authData = authManager.authData!;
// If a [layoutID] is not null, then that layout must be prioritized and
// downloaded first if it is not already cached.
//
// If the publish model is still null, then that needs to be downloaded
// first, and we do that by getting the first event of the stream below.
//
// Once a publish model is available in the future, if a [layoutID] is
// specified, we try to download that layout then instead of here.
final bool didPrepareLayout;
if (_publishModel != null && layoutID != null) {
if (!_publishModel!.layouts.containsKey(layoutID)) {
// If the layout is disabled, we skip it.
if (publishModel!.disabledLayouts.contains(layoutID)) {
DebugLogger.instance.printInfo(
'Layout [$layoutID] is marked as disabled. Skipping it during init.',
name: name,
);
didPrepareLayout = false;
_logTime(stopwatch);
} else {
DebugLogger.instance.printInfo(
'Layout [$layoutID] during init is not cached locally. Downloading...',
name: name,
);
try {
didPrepareLayout = await getOrFetchPopulatedLayout(
layoutID: layoutID,
);
DebugLogger.instance.printInfo(
'Layout in init [$layoutID] fetch complete.',
name: name,
);
} catch (e, str) {
DebugLogger.instance.log(
'Layout [$layoutID] failed to download during init.\nError: $e',
category: DebugCategory.error,
name: name,
level: Level.WARNING,
);
ErrorLogger.instance.captureException(
e,
message: 'Failed to download layout [$layoutID] during init.',
type: 'layout_download_failed',
layoutID: layoutID,
stackTrace: str,
);
_logTime(stopwatch);
return;
}
}
} else {
DebugLogger.instance.printInfo(
'Layout [$layoutID] during init is already cached locally. Skipping layout download.',
name: name,
);
didPrepareLayout = true;
}
} else {
if (_publishModel == null) {
DebugLogger.instance.printInfo(
'Publish model during init is not cached locally. Going to wait for the first publish model from the server.',
name: name,
);
} else if (layoutID == null) {
DebugLogger.instance.printInfo(
'Publish model during init is available and layoutID is not specified. All layouts will be downloaded soon!',
name: name,
);
} else {
DebugLogger.instance.printInfo(
'Publish model during init is available and layoutID is specified. Layout [$layoutID] will be downloaded soon from stream.',
name: name,
);
}
didPrepareLayout = false;
}
// Listen the publish model document.
// It's either going to be fetched for the first time if it doesn't exist
// in cache, or it's going to be updated with new data.
final Future<SDKPublishModel> firstPublishEvent = listenToPublishModel(
authData.projectId,
);
if (_publishModel != null) {
// The cached model is used and the first event is not awaited. Its
// errors are already reported by the stream listener.
firstPublishEvent.ignore();
}
// If the publish model is still null, then we need to wait for the first
// publish model to arrive from the server via the stream above.
if (_publishModel == null) {
DebugLogger.instance.printInfo(
'Publish model is still null during init. Waiting for the first publish model from the server.',
name: name,
);
final model = await firstPublishEvent;
_publishModel = model;
await emitPublishModel();
savePublishModel();
DebugLogger.instance.printInfo(
'Publish model during init is now available. Proceeding with init!',
name: name,
);
if (_publishModel == null) {
DebugLogger.instance.printInfo(
'Publish model is still null.\n'
'Is there a network problem or bad authentication?',
name: name,
);
_logTime(stopwatch);
return;
}
}
// If a [layoutID] was specified, then that layout must be prioritized and
// downloaded first if it is not already cached.
//
// If we could not download it earlier, that would be because we did not
// have a publish model available and needed to wait for one to arrive
// from the server for the first time or that it failed to download for
// some unknown reason.
//
// Perhaps the publish model was simply out of date locally,
// but now that we fetched a new one, and [didPrepareLayout] is still
// false, we can try to download the layout again with the new publish
// model.
//
// At this stage of the function, we can be sure that a publish model
// exists and can safely download the desired [layoutID], because if a
// publish model is still null, we cannot proceed further and this
// function terminates earlier.
if (!didPrepareLayout && layoutID != null) {
DebugLogger.instance.printInfo(
'We can safely download layout [$layoutID] now.',
name: name,
);
if (publishModel!.disabledLayouts.contains(layoutID)) {
DebugLogger.instance.printInfo(
'Layout [$layoutID] is disabled. Skipping it during init.',
name: name,
);
}
try {
await getOrFetchPopulatedLayout(layoutID: layoutID);
DebugLogger.instance.printInfo(
'Layout [$layoutID] downloaded from init successfully.',
name: name,
);
} catch (e, str) {
ErrorLogger.instance.captureException(
e,
message: 'Failed to download layout [$layoutID] during init.',
type: 'layout_download_failed',
layoutID: layoutID,
stackTrace: str,
);
// _logTime(stopwatch);
// return;
}
}
// Add all the layouts to the download queue excluding the [layoutID] if
// that was specified. We don't want to download that layout twice.
if (config.preload) {
DebugLogger.instance.printInfo(
'Config preload was specified during init, adding ${_publishModel!.updates.layouts.length - 1} layouts to the download queue...',
name: name,
);
_downloadQueue.addAll(
[..._publishModel!.updates.layouts.keys]..remove(layoutID),
);
DebugLogger.instance.printInfo(
'All layouts during init download complete. ${_downloadQueue.length} layouts in queue.',
name: name,
);
}
// If a [layoutID] was specified for this initialization, then the Future
// callback of this init function must complete once the layout has been
// downloaded.
// Otherwise we need to await for all layouts to be downloaded before
// completing the Future.
if (layoutID != null) {
DebugLogger.instance.printInfo(
'Layout [$layoutID] was specified during init. Processing download queue in the background...',
name: name,
);
processDownloadQueue();
} else {
DebugLogger.instance.printInfo(
'No layout was specified during init. Awaiting all layouts to be downloaded...',
name: name,
);
await processDownloadQueue();
}
purgeDisabledLayouts(notify: true);
_logTime(stopwatch);
}