Skip to content
This repository has been archived by the owner on Feb 9, 2024. It is now read-only.

Commit

Permalink
Split root widget to fix wrong usage (#20)
Browse files Browse the repository at this point in the history
The current widget `App` uses two nested `FutureBuilder`s in a wrong
way: The provided future should not be constructed inside the build
method. As the
[documentation](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html#managing-the-future)
puts it:

> The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateWidget, or State.didChangeDependencies.
It must not be created during the State.build or StatelessWidget.build
method call when constructing the FutureBuilder. If the future is
created at the same time as the FutureBuilder, then every time the
FutureBuilder's parent is rebuilt, the asynchronous task will be
restarted.

This behavior has been observed during development as flickering and
crashing due to duplicate network activation.

With this change the two `FutureBuilder`s now live in their own separate
stateful widgets where the futures are constructed by their respective
`initState` methods.

This change has the furtunate side effect of making the original widget
much simpler and easier to understand.

---------

Co-authored-by: Søren Schwartz <[email protected]>
  • Loading branch information
bisgardo and Søren Schwartz authored Nov 26, 2023
1 parent 48d0c29 commit 99f2fd7
Showing 1 changed file with 93 additions and 38 deletions.
131 changes: 93 additions & 38 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,53 +50,108 @@ class App extends StatelessWidget {

@override
Widget build(BuildContext context) {
// Initialize configuration and service repository.
return _WithServiceRepository(
child: _WithSelectedNetwork(
initialNetwork: initialNetwork,
child: MultiBlocProvider(
providers: [
BlocProvider(
create: (context) {
// Initialize T&C by loading the currently accepted version from shared preferences.
final prefs = context.read<ServiceRepository>().sharedPreferences;
return TermsAndConditionAcceptance(prefs);
},
),
],
child: MaterialApp(
routes: appRoutes,
theme: concordiumTheme(),
),
),
),
);
}
}

class _WithServiceRepository extends StatefulWidget {
final Widget child;

const _WithServiceRepository({required this.child});

@override
State<_WithServiceRepository> createState() => _WithServiceRepositoryState();
}

class _WithServiceRepositoryState extends State<_WithServiceRepository> {
late final Future<ServiceRepository> _bootstrapping;

@override
void initState() {
super.initState();
setState(() {
_bootstrapping = bootstrap();
});
}

@override
Widget build(BuildContext context) {
return FutureBuilder<ServiceRepository>(
future: bootstrap(),
future: _bootstrapping,
builder: (_, snapshot) {
final services = snapshot.data;
if (services == null) {
// Initializing configuration and service repository.
return const _Initializing();
}
// Provide initialized service repository to the nested components
// (including the blocs created in the child provider).
// Then activate the initial network (starting services related to that network).
return RepositoryProvider(
create: (_) => services,
child: FutureBuilder(
future: services.activateNetwork(initialNetwork),
builder: (_, snapshot) {
final networkServices = snapshot.data;
if (networkServices == null) {
// Initializing network services.
return const _Initializing();
}

// Initialize blocs/cubits.
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) {
// Initialize selected network as the one that was just activated.
return SelectedNetwork(networkServices);
},
),
BlocProvider(
create: (context) {
// Initialize T&C by loading the currently accepted version from shared preferences.
final prefs = context.read<ServiceRepository>().sharedPreferences;
return TermsAndConditionAcceptance(prefs);
},
),
],
child: MaterialApp(
routes: appRoutes,
theme: concordiumTheme(),
),
);
},
),
child: widget.child,
);
},
);
}
}

class _WithSelectedNetwork extends StatefulWidget {
final NetworkName initialNetwork;
final Widget child;

const _WithSelectedNetwork({required this.initialNetwork, required this.child});

@override
State<_WithSelectedNetwork> createState() => _WithSelectedNetworkState();
}

class _WithSelectedNetworkState extends State<_WithSelectedNetwork> {
late final Future<NetworkServices> _activating;

@override
void initState() {
super.initState();
final services = context.read<ServiceRepository>();
setState(() {
_activating = services.activateNetwork(initialNetwork);
});
}

@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _activating,
builder: (_, snapshot) {
final networkServices = snapshot.data;
if (networkServices == null) {
// Initializing network services.
return const _Initializing();
}

// Initialize blocs/cubits.
return BlocProvider(
create: (_) {
// Initialize selected network as the one that was just activated.
return SelectedNetwork(networkServices);
},
child: widget.child,
);
},
);
Expand Down

0 comments on commit 99f2fd7

Please sign in to comment.