Im trying to add the Google Android Consent SDK to Flutter and connect to it using a MethodChannel. I've got the form popping up successfully and I am able to return some info back to my main.dart file on the Flutter side.
I'm having trouble getting the users choice they selected from the Google Consent Form returned back to me to the Flutter side so I can then save whether they selected to see PERSONALIZED or NON-PERSONALIZED ads back in my main.dart file. Im just using the boilerplate Flutter example app to achieve this. Any help is greatly appreciated.
main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
static const CHANNEL = const MethodChannel("flutter.native/helper");
static const IS_EEA_OR_UNKNOWN_METHOD = "isEeaOrUnknown";
bool isEeaOrUnknown = true;
Future<bool> _isEeaOrUnknown() async {
var result = await CHANNEL.invokeMethod(IS_EEA_OR_UNKNOWN_METHOD);
if (result is bool) {
print("isEEAOrUnknown: $result");
return result;
} else {
print("WTF: $result");
return true;
}
}
void _callIsEea() {
_isEeaOrUnknown().then((result) {
Future.delayed(Duration(seconds: 3)).then((d) {
setState(() {
isEeaOrUnknown = result;
});
});
});
}
#override
void initState(){
super.initState();
_callIsEea();
}
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"iisEeaOrUnknown: $isEeaOrUnknown",
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
MainActivity.kt
package com.example.flutter_consent
import android.os.Bundle
import com.google.ads.consent.DebugGeography
import com.google.ads.consent.ConsentStatus
import com.google.ads.consent.ConsentInfoUpdateListener
import com.google.ads.consent.ConsentInformation
import com.google.ads.consent.ConsentFormListener
import com.google.ads.consent.ConsentForm
import io.flutter.app.FlutterActivity
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import java.net.MalformedURLException
import java.net.URL
class MainActivity: FlutterActivity() {
var form: ConsentForm? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
print("NATIVE method call")
when (call.method) {
IS_EEA_METHOD_NAME -> {
print("NATIVE METHOD CALLED - $IS_EEA_METHOD_NAME")
isEeaOrUnknown(result)
}
else -> {
print("NATIVE METHOD CALL ERROR")
result.notImplemented()
}
}
}
}
private fun isEeaOrUnknown(result: MethodChannel.Result){
val consentInformation = ConsentInformation.getInstance(this)
//testing only
consentInformation.debugGeography = DebugGeography.DEBUG_GEOGRAPHY_EEA
//consentInformation.debugGeography = DebugGeography.DEBUG_GEOGRAPHY_NOT_EEA
consentInformation.requestConsentInfoUpdate(arrayOf(PUBLISHER_ID), object : ConsentInfoUpdateListener {
override fun onConsentInfoUpdated(consentStatus: ConsentStatus) {
when (consentStatus) {
ConsentStatus.PERSONALIZED -> {
print("User selected personalized")
}
ConsentStatus.NON_PERSONALIZED -> {
print("User non-personalized")
}
ConsentStatus.UNKNOWN -> {
print("UNKNOWN")
}
}
}
override fun onFailedToUpdateConsentInfo(errorDescription: String) {
print("ERROR $errorDescription")
result.success(consentInformation.isRequestLocationInEeaOrUnknown)
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
})
var privacyUrl: URL? = null
try {
privacyUrl = URL("http://www.privacyurl.com/")
} catch (e: MalformedURLException) {
e.printStackTrace()
// Handle error.
}
form = ConsentForm.Builder(this, privacyUrl).withListener(object : ConsentFormListener() {
override fun onConsentFormLoaded() {
// Consent form loaded successfully.
form!!.show()
}
override fun onConsentFormOpened() {
// Consent form was displayed.
}
override fun onConsentFormClosed(
consentStatus: ConsentStatus?, userPrefersAdFree: Boolean?) {
// Consent form was closed.
}
override fun onConsentFormError(errorDescription: String?) {
// Consent form error.
}
})
.withPersonalizedAdsOption()
.withNonPersonalizedAdsOption()
.build()
form!!.load()
}
companion object{
const val CHANNEL = "flutter.native/helper"
const val PUBLISHER_ID = "pub-xxxxxxxxxxxxxxx"
const val IS_EEA_METHOD_NAME = "isEeaOrUnknown"
}
}
Related
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'UI_Tool/size_fit.dart';
class Gallery extends StatefulWidget {
#override
_GalleryState createState() => _GalleryState();
}
class _GalleryState extends State<Gallery> {
pic() async {
var url = "http://120.76.247.131:8081/findAllImages";
var response = await http.get(Uri.parse(url));
return json.decode(response.body);
}
#override
void initState() {
super.initState();
pic();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Gallery'),
),
body: FutureBuilder(
future : pic(),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? ListView.builder(
itemCount:2,
itemBuilder: (context, index) {
List list = pic() as List;
return Card(
child: ListTile(
title: Container(
width: 100,
height: 100,
child: Image.network(
"http://120.76.247.131:8081/findAllImages/%7Blist[index][%22image%22]%7D%22)"
),
),
));
})
: Center(
child: CircularProgressIndicator(),
);
},
),
);
}
}
I had tried to add behind the future and it doesn't solve the problem. Moreover, there is a problem with the itemcount, so I left it with a number instead of add snapshot.data!.length() because I am not sure why there is an error with snapshot.data!.length() for itemcount.
Here is full working code.
At first because pic() returns Future,
you need to use 'await' or 'then' to get a response.
https://dart.dev/codelabs/async-await
Because of that, below sentence cause error as you provided.
But because it is not necessary in this case, I get rid of this.
List list = pic() as List;
If you want use pic() method, just call like below.
(But in this case, you cannot call like this.)
List list = await pic()
You've already used 'FutureBuilder', you don't need to call pic() again.
You just use snapdata's data incase of snapdata has a data.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
print('onStart');
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Gallery(),
);
}
}
class Gallery extends StatefulWidget {
#override
_GalleryState createState() => _GalleryState();
}
class _GalleryState extends State<Gallery> {
pic() async {
var url = "http://120.76.247.131:8081/findAllImages";
var response = await http.get(Uri.parse(url));
return json.decode(response.body);
}
#override
void initState() {
super.initState();
pic();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Gallery'),
),
body: FutureBuilder(
future: pic(),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? ListView.builder(
itemCount: (snapshot.data! as Map)['data'].length,
itemBuilder: (context, index) {
// List list = pic() as List;
print((snapshot.data! as Map)['data'][index]);
return Card(
child: ListTile(
title: Container(
width: 100,
height: 100,
child: Image.network(
(snapshot.data! as Map)['data'][index]['image']),
),
));
})
: Center(
child: CircularProgressIndicator(),
);
},
),
);
}
}
i am working on an online shopping application using retrofit, coroutine, livedata, mvvm,...
i want to show progress bar before fetching data from server for afew seconds
if i have one api request i can show that but in this app i have multiple request
what should i do in this situation how i should show progress bar??
Api Service
#GET("homeslider.php")
suspend fun getSliderImages(): Response<List<Model.Slider>>
#GET("amazingoffer.php")
suspend fun getAmazingProduct(): Response<List<Model.AmazingProduct>>
#GET("handsImages.php")
suspend fun getHandsFreeData(
#Query(
"handsfree_id"
) handsfree_id: Int
): Response<List<Model.HandsFreeImages>>
#GET("handsfreemoreinfo.php")
suspend fun gethandsfreemoreinfo(): Response<List<Model.HandsFreeMore>>
#GET("wristmetadata.php")
suspend fun getWristWatchMetaData(
#Query(
"wrist_id"
) wrist_id: Int
): Response<List<Model.WristWatch>>
repository
fun getSliderImages(): LiveData<List<Model.Slider>> {
val data = MutableLiveData<List<Model.Slider>>()
val job = Job()
applicationScope.launch(IO + job) {
val response = api.getSliderImages()
withContext(Main + SupervisorJob(job)) {
data.value = response.body()
}
job.complete()
job.cancel()
}
return data
}
fun getAmazingOffer(): LiveData<List<Model.AmazingProduct>> {
val data = MutableLiveData<List<Model.AmazingProduct>>()
val job = Job()
applicationScope.launch(IO + job) {
val response = api.getAmazingProduct()
withContext(Main + SupervisorJob(job)) {
data.value = response.body()
}
job.complete()
job.cancel()
}
return data
}
fun getHandsFreeData(handsree_id: Int): LiveData<List<Model.HandsFreeImages>> {
val dfData = MutableLiveData<List<Model.HandsFreeImages>>()
val job = Job()
applicationScope.launch(IO + job) {
val response = api.getHandsFreeData(handsree_id)
withContext(Main + SupervisorJob(job)) {
dfData.value = response.body()
}
job.complete()
job.cancel()
}
return dfData
}
fun getHandsFreeMore(): LiveData<List<Model.HandsFreeMore>> {
val data = MutableLiveData<List<Model.HandsFreeMore>>()
val job = Job()
applicationScope.launch(IO + job) {
val response = api.gethandsfreemoreinfo()
withContext(Main + SupervisorJob(job)) {
data.value = response.body()
}
job.complete()
job.cancel()
}
return data
}
VIEWMODEL
fun getSliderImages() = repository.getSliderImages()
fun getAmazingOffer() = repository.getAmazingOffer()
fun recieveAdvertise() = repository.recieveAdvertise()
fun dailyShoes(context: Context) = repository.getDailyShoes(context)
i will appreciate your help
I couldn't help but notice that your repository contains lots of repetitive code. first point to learn here is that all that logic in Repository, it usually goes in the ViewModel. second thing is that you are using applicationScope to launch your coroutines, which usually is done using viewModelScope(takes care of cancellation) object which is available in every viewModel.
So first we have to take care of that repetitive code and move it to ViewModel. So your viewModel would now look like
class YourViewModel: ViewModel() {
// Your other init code, repo creation etc
// Live data objects for progressBar and error, we will observe these in Fragment/Activity
val showProgress: MutableLiveData<Boolean> = MutableLiveData()
val errorMessage: MutableLiveData<String> = MutableLiveData()
/**
* A Generic api caller, which updates the given live data object with the api result
* and internally takes care of progress bar visibility. */
private fun <T> callApiAndPost(liveData: MutableLiveData<T>,
apiCall: () -> Response<T> ) = viewModelScope.launch {
try{
showProgress.postValue(true) // Show prgress bar when api call is active
if(result.code() == 200) { liveData.postValue(result.body()) }
else{ errorMessage.postValue("Network call failed, try again") }
showProgress.postValue(false)
}
catch (e: Exception){
errorMessage.postValue("Network call failed, try again")
showProgress.postValue(false)
}
}
/******** Now all your API call methods should be called as *************/
// First declare the live data object which will contain the api result
val sliderData: MutableLiveData<List<Model.Slider>> = MutableLiveData()
// Now call the API as
fun getSliderImages() = callApiAndPost(sliderData) {
repository.getSliderImages()
}
}
After that remove all the logic from Repository and make it simply call the network methods as
suspend fun getSliderImages() = api.getSliderImages() // simply delegate to network layer
And finally to display the progress bar, simply observe the showProgress LiveData object in your Activity/Fragment as
viewModel.showProgress.observer(this, Observer{
progressBar.visibility = if(it) View.VISIBLE else View.GONE
}
First create a enum class status:
enum class Status {
SUCCESS,
ERROR,
LOADING
}
Then create resource class like this:
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(msg: String, data: T?): Resource<T> {
return Resource(Status.ERROR, data, msg)
}
fun <T> loading(data: T?): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
Now add your request to a list of response:
var list = java.util.ArrayList<Response<*>>()
suspend fun getApis() = list.addAll(
listOf(
api.advertise(),
api.getAmazingProduct(),
api.dailyShoes(),
api.getSliderImages(),
.
.
.
)
)
In your viewmodel class:
private val _apis = MutableLiveData<Resource<*>>()
val apis: LiveData<Resource<*>>
get() = _apis
init {
getAllApi()
}
fun getAllApi() {
val job = Job()
viewModelScope.launch(IO + job) {
_apis.postValue(
Resource.loading(null)
)
delay(2000)
repository.getApis().let {
withContext(Main + SupervisorJob(job)) {
it.let {
if (it) {
_apis.postValue(Resource.success(it))
} else {
_apis.postValue(Resource.error("Unknown error eccured", null))
}
}
}
}
job.complete()
job.cancel()
}
}
Now you can use status to show progress like this . use this part in your target fragment:
private fun setProgress() {
viewModel.apis.observe(viewLifecycleOwner) {
when (it.status) {
Status.SUCCESS -> {
binding.apply {
progress.visibility = View.INVISIBLE
line1.visibility = View.VISIBLE
parentscroll.visibility = View.VISIBLE
}
}
Status.ERROR -> {
binding.apply {
progress.visibility = View.INVISIBLE
line1.visibility = View.INVISIBLE
parentscroll.visibility = View.INVISIBLE
}
}
Status.LOADING -> {
binding.apply {
progress.visibility = View.VISIBLE
line1.visibility = View.INVISIBLE
parentscroll.visibility = View.INVISIBLE
}
}
}
}
}
I hope you find it useful.
loadSingle return Single object, if it fails I want to call getObservable(rsList) which return Observable.
I am trying with onErrorResumeNext but it needs Single object.
How can I call getObservable(rsList) on failure of loadSingle() ?
Thanks in advance!!
repo.loadSingle()
.subscribeOn(Schedulers.io())
.onErrorResumeNext {
repo.getObservable(rsList)
}
.flatMapObservable {
if (it != null && it.status == Status.SUCCESS) {
upRsList(it.data)
}
repo.getObservable(rsList)
}
({ //observable success
}, {
//observable error
})
Api interface
interface HomeApi{
fun getSingel():Single<List<String>>
fun getObservable():Observable<HomeResponse>
}
Dependencies
testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.6.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.2")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.6.2")
implementation "io.reactivex.rxjava3:rxjava:3.0.4"
implementation "io.reactivex.rxjava3:rxkotlin:3.0.0"
Required classes
internal interface Repo {
fun loadSingle(): Single<Result<List<String>>>
fun getObservable(list: List<String>): Observable<String>
}
internal class RepoImpl : Repo {
override fun loadSingle(): Single<Result<List<String>>> {
return Single.error(RuntimeException("fail"))
}
override fun getObservable(list: List<String>): Observable<String> {
if (list === emptyList<String>()) {
return Observable.just("42")
}
return Observable.just("success")
}
}
internal sealed class Result<T> {
data class Success<T>(val value: T) : Result<T>()
data class Failure<T>(private val failure: Throwable) : Result<T>()
}
Test
Wrap the error via #onErrorReturn into a default value, and handle the result accordingly.
class So64751341 {
#Test
fun `64751341`() {
val repo: Repo = RepoImpl()
val testScheduler = TestScheduler()
val flatMapObservable = repo.loadSingle()
.subscribeOn(testScheduler)
.onErrorReturn { failure -> Result.Failure(failure) }
.flatMapObservable { result ->
when (result) {
is Result.Success -> repo.getObservable(result.value)
is Result.Failure -> repo.getObservable(emptyList())
}
}
val test = flatMapObservable.test()
testScheduler.triggerActions()
test // return default value 42 onError
.assertValue("42")
}
}
Repo#loadSingle() throws exception synchronously
internal class RepoExceptionImpl : Repo {
override fun loadSingle(): Single<Result<List<String>>> {
throw java.lang.RuntimeException("whatever")
}
override fun getObservable(list: List<String>): Observable<String> {
if (list === emptyList<String>()) {
return Observable.just("42")
}
return Observable.just("success")
}
}
Test
Repo#loadSingle must be wrapped with Single#defer. Single#defer will catch the exception and emit it as #onError to the subscriber, which in turn will be handled by #onErrorReturn
#Test
fun `64751341_exception`() {
val repo: Repo = RepoExceptionImpl()
val testScheduler = TestScheduler()
val flatMapObservable = Single.defer {
repo.loadSingle()
}
.subscribeOn(testScheduler)
.onErrorReturn { failure -> Result.Failure(failure) }
.flatMapObservable { result ->
when (result) {
is Result.Success -> repo.getObservable(result.value)
is Result.Failure -> repo.getObservable(emptyList())
}
}
val test = flatMapObservable.test()
testScheduler.triggerActions()
test // return default value 42 onError
.assertValue("42")
}
I want to add some java code to flutter module。Which directory should I add java code.
I have tried to add java code in the directory as picture bottom,but the code will not be compiled to aar.
You should implement platform channel for executing some java code.
For Example, I am passing two int value from a flutter to Android Native. In the Android native side, Kotlin/Java will sum these numbers and return the result to flutter
Flutter side
class Demo extends StatefulWidget {
#override
_DemoState createState() => _DemoState();
}
class _DemoState extends State<Demo> {
static const platform = const MethodChannel('flutter.native/helper');
#override
void initState() {
super.initState();
}
getData(int num1, int num2) async {
try {
final int result =
await platform.invokeMethod('add', {'num1': num1, 'num2': num2});
print('$result');
} on PlatformException catch (e) {}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Country"),
SizedBox(
height: 20.0,
),
RaisedButton(
onPressed: () => getData(10, 20),
child: Text('Add'),
)
],
),
),
),
);
}
}
Android native side
class MainActivity : FlutterActivity() {
private val CHANNEL = "flutter.native/helper"
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "add") {
val num1 = call.argument<Int>("num1")
val num2 = call.argument<Int>("num2")
val sum = num1!! + num2!!
result.success(sum)
} else {
result.notImplemented()
}
}
}
}
Note: You should do the same thing in iOS native side or execute this method if the device is Android otherwise it will crash in iOS device
i was working on some project with flutter that has to fetch data from the internet i tried to get it done using dart but it was not possible so i decided to do it in java and it works the problem the native java code started executing before the loading screen shows up on the screen and it shows some white screen and after that it just goes to the homepage is there anyway that i can make my loading screen appear first and then load the data from the internet as the loading screen is showing while the user waits and push the screen to the home screen after the loading is over.
i have tried putting sleep Duration for one second so that it can render the loading screen first but it didn't work.
Here is my java code
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "samples.flutter.dev/battery";
#Override
public void configureFlutterEngine( FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
if (call.method.equals("getDate")) {
try {
result.success(getReal2());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
else {
result.notImplemented();
}
}
);
}
public static List getCoffee() throws IOException {
ArrayList coffee = new ArrayList();
Document doc;
doc= Jsoup.connect("https://www.bankofabyssinia.com").timeout(6000).get();
for(int i=0;i<=8;i++) {
for (int j=2;j<=3;j++){
coffee.add(doc.select(" #myTable > tbody > tr:nth-child("+i+") > td:nth-child("+j+")").text());
}
}
return coffee;
}
#TargetApi(Build.VERSION_CODES.N)
public List getReal2() throws ExecutionException, InterruptedException {
CompletableFuture<List > completableFuture = CompletableFuture.supplyAsync(() -> {
try {
return getCoffee();
} catch (IOException e) {
e.printStackTrace();
}
return null;
});
while (!completableFuture.isDone()) {
}
List result = completableFuture.get();
return result;
}
}
and here is my flutter code
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'Home.dart';
class Load extends StatefulWidget {
#override
_LoadState createState() => _LoadState();
}
class _LoadState extends State<Load> {
static const platform = const MethodChannel('samples.flutter.dev/battery');
void getWholeJava() async {
try {
final List result = await platform.invokeMethod('getDate');
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => Home(
whole: result,
)));
} on PlatformException catch (e) {
print(e);
}
}
#override
void initState() {
// TODO: implement initState
super.initState();
getWholeJava();
}
#override
Widget build(BuildContext context) {
return Scaffold(body: Builder(builder: (BuildContext context) {
return Container(
child: Center(
child: Column(
children: <Widget>[
Text(
'The app is loading',
style: TextStyle(
fontSize: 40.0,
fontWeight: FontWeight.w900,
color: Colors.green[900]),
),
],
)),
);
}));
}
}
First, android will default to its launch screen, which is managed by launch_background.xml inside the template flutter android project. This means that you will need to use a background color for the splash screen, that will work with the android native launch screen drawable.
Next, to implement a splash screen, that will then navigate to a page, where you can run and load your data, you can look at this:
import 'package:async/async.dart';
class SampleSplashScreenPage extends StatefulWidget {
#override
_SampleSplashScreenPageState createState() => new _SampleSplashScreenPageState();
}
class _SampleSplashScreenPageState extends State<SampleSplashScreenPage> {
#override
void initState() {
super.initState();
new RestartableTimer(new Duration(seconds: 3), () async {
await Navigator.push(context, MaterialPageRoute(builder: (_) {
return FlutterDataLoadingPage();
}));
});
}
#override
Widget build(BuildContext context) {
return new Text("Your splash screen");
}
RestartableTimer is a class that will begin a stopwatch, and will count up to the designated duration. Once this duration is reached, it will then invoke the handler you pass to it, which in this case is the navigation logic to the page responsible for reading the remote data. This counter by the way does not restart, and will automatically be disposed by the garbage collector.
Take a look at the documentation to learn more:
https://api.flutter.dev/flutter/package-async_async/RestartableTimer-class.html