I have one FutureBuilder
that has a basic structure.
FutureBuilder(
future: api.sincronizacion(),
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
...
})
The problem that I present is that for some strange reason it executes the method at least 2 times sincronizacion()
, this causes me to generate errors with some other methods such as insertions to the database with the same one id
that PRIMARY KEY
generates an exception.
One of the exceptions is received.
SqfliteDatabaseException (DatabaseException(UNIQUE constraint failed: versions._id (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY[1555])) sql 'INSERT INTO versions (_id, stability, majorversion, minorversion, revision) VALUES (?, ?, ?, ?, ?)' args [5dc08909cad9c242119da306, beta, 1, 0, 12]})
Another exception that occurs
Exception has occurred. SqfliteDatabaseException (DatabaseException(database_closed 1))
Simple Example
This example simulates the simple execution of what I think is the problem with Future
using a variable number
to count the number of times the method is called without restarting the instance.
At the end of the load in data it returns 3,2 and on one occasion 4 which should only be 1 because only 1 call to the method _getData
that increases is expectednumber
main.dart
void main() => runApp(MyMaterialApp());
class MyMaterialApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('Ejecutando MyMaterialApp');
return MaterialApp(
home: SplashLoad(),
);
}
}
class SplashLoad extends StatefulWidget {
@override
_SplashLoadState createState() => _SplashLoadState();
}
class _SplashLoadState extends State<SplashLoad> {
final apiSimulation = new ApiSimulation();
@override
Widget build(BuildContext context) {
print('SplashScreen');
return Scaffold(
body: Container(
child: Center(
child: FutureBuilder(
future: apiSimulation.sincronizacion(),
// initialData: InitialData,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData) {
return Text('Carga completa data:${snapshot.data}');
}
return CircularProgressIndicator();
},
),
),
),
);
}
}
ApiSimulation
it simulates a bit what it would be like to call about 4 seconds to get data from a server and add it to a database.
class ApiSimulation {
int number = 0;
Future<int> sincronizacion()async{
print('INICIANDO SINCRONIZACIÓN');
await Future.delayed(Duration(seconds: 2));
final data = await _getData();
return data;
}
Future<int> _getData() async{
number++;
print('ejecutando getData$number');
await Future.delayed(Duration(seconds: 2));
return number;
}
}
Result in console
I/flutter (32404): Ejecutando MyMaterialApp
I/flutter (32404): SplashScreen
I/flutter (32404): INICIANDO SINCRONIZACIÓN
I/flutter (32404): Ejecutando MyMaterialApp
I/flutter (32404): SplashScreen
I/flutter (32404): INICIANDO SINCRONIZACIÓN
I/flutter (32404): ejecutando getData1
I/flutter (32404): ejecutando getData2
end of example
I have done multiple tests
- Follow the execution of the code line by line with the VisualStudioCode tool
- Changing the method implementation from
static Future<List> sincronizacion() async {...
toFuture<List> sincronizacion() async {
just in case helps - Modify my
main
thinking that the way the interface was called would cause it to be called 2 times and that at the same time execute againsincronizacion()
- Previously
MaterialApp( localizationsDelegates: [CustomLocalizationDelegate()], routes: getApplicationRoutes(), home: MyApp(), ...
- After where
getApplicationRoutes()
containsMyApp()
in the path'/'
MaterialApp( localizationsDelegates: [CustomLocalizationDelegate()], routes: getApplicationRoutes(), initialRoute: '/', ...
- Previously
The following classes and methods are from my project that exhibits the problem.
main
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final auth = Auth();
final prefs = new PreferenciasApp(); //singleton
await prefs.initPrefs();
runApp(Provider(
auth: auth,
child: MaterialApp(
localizationsDelegates: [CustomLocalizationDelegate()],
routes: getApplicationRoutes(),
initialRoute: '/',
theme: ThemeData(...),
),
));
}
getApplicationRoutes() in routes.dart file
// imports
...
final main = MyApp();
final home = Home(selectBottom: 0,);
final registro = Registro();
final inicioSesion = InicioSesionPlussUltraPower();
final playlist = PlaylisPage();
final inicio = Inicio();
final detallePlay = DetallePlay();
Map<String, WidgetBuilder> getApplicationRoutes() {
return <String, WidgetBuilder>{
"/" : (BuildContext context) => main,
"/Himnario" : (BuildContext context) => home,
"/Registro" : (BuildContext context) => registro,
"/Inicio de sesión" : (BuildContext context) => inicioSesion,
"/PlayList" : (BuildContext context) => playlist,
"/Inicio" : (BuildContext context) => inicio,
DetallePlay.routeName : (BuildContext context) => detallePlay,
};
}
My App
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
final prefs = new PreferenciasApp();
final api = new ApiService();
@override
Widget build(BuildContext context) {
print('Ejecutando myApp------------------------->>>>>');
return FutureBuilder(
future: api.sincronizacion(),
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if (snapshot.hasData) {
final result = snapshot.data;
if (result[0] == null) {
return Container(...),
);
} else {
text = result[0] ? 'Carga completa' : 'Error en la carga.';
var page = prefs.idUser.isEmpty ? Inicio() : InicioSesion();
return SplashScreen(
seconds: 1,
navigateAfterSeconds: page,
...
);
}
} //snapshot.hasData
return Container(...);
},
); // FutureBuilder
}
}
3 things:
1- Convert
MyApp
to StatefulWidget.2- Extract
final prefs = new PreferenciasApp();
andfinal api = new ApiService();
outside the methodbuild
and inside the State that you will create.3- Get the variables you have inside the method
getApplicationRoutes
.You are recreating the
ApiService
so itFutureBuilder
detects that they are different instances and methods that you are calling.UPDATE
After a little research, I found that there are 2 bugs, but from Flutter.
1- For some reason the root widget is refreshing many times, I leave the reported link that was closed for no reason: https://github.com/flutter/flutter/issues/23063
2- The FutureBuilder is recreating the future, this should not happen, although point 1 could happen, but
FutureBuilder
it should keep the future that was executed and not execute it again.Solution
Extract the
Future
in theinitState
, as follows.Apply it to your project and if it works let us know :).