splash screen and one time intro in flutter - dart

I want my splash screen to always appear in my application and it does which is great, but I have a walk through after the splash screen and I want it to be a one time walk through, So i want to add an integer to the shared preferences with a value of 0 and everytime I open the splash screen the value is incremented by one, so when "number" equals 1 or greater at the second run the splash screen skips the walkthrough and goes to home , here is the code that I want to edit now :
void initState() {
// TODO: implement initState
super.initState();
Timer(Duration(seconds: 5), () => MyNavigator.goToIntro(context));
}
And I want it to be like :
void initState() {
// TODO: implement initState
super.initState();int number=0;//this is in the shared prefs
Timer(Duration(seconds: 5), () => if(number==0){MyNavigator.goToIntro(context));
}else{MyNavigator.goToHome(context));
number++;}
}

The below code prints perfectly as we expect(during first launch only, "First launch"). You can use your navigation logic instead of print.
#override
void initState() {
super.initState();
setValue();
}
void setValue() async {
final prefs = await SharedPreferences.getInstance();
int launchCount = prefs.getInt('counter') ?? 0;
prefs.setInt('counter', launchCount + 1);
if (launchCount == 0) {
print("first launch"); //setState to refresh or move to some other page
} else {
print("Not first launch");
}
}

We need to have the number value to be saved across multiple app launches. We can use shared_preference plugin to achieve this.

secondly, getData that saved in our device.
Future<bool> getSaveData() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
bool isIntroScreenOpenedBefore =
sharedPreferences.getBool("isIntroScreenOpened") ?? false;
print(sharedPreferences.containsKey("isIntroScreenOpened")); // check your key either it is save or not?
if (isIntroScreenOpenedBefore == true) {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return LoginBoard();
}));
} else {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return WalKThroughScreen();
}));
}
return isIntroScreenOpenedBefore;
}

at first, let's save the data as boolean
Future<void> saveData() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
bool isIntroScreenOpened = true;
sharedPreferences.setBool("isIntroScreenOpened", isIntroScreenOpened); // saved data to your device.
}

Answer by #Dinesh Balasubramanian is works really fine.
But I have 4 initial screen that need to show once. I have done that using same logic in each screen. and then my app was showing 5th screen second time like fast forwarding all the previous screen and stopping on 5th screen.
To resolve this I am getting all the set Preferences at main.dart to open directly 5th screen. but when I do that I am having this problem,
"E/flutter (32606): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)]
Unhandled Exception: Navigator operation requested with a context that
does not include a Navigator.
E/flutter (32606): The context used to
push or pop routes from the Navigator must be that of a widget that is
a descendant of a Navigator widget."
Here is code to switch from main.dart:
int firstLogin, firstMobile, firstOtp, firstInfo;
void setValue() async {
final prefs = await SharedPreferences.getInstance();
firstLogin = prefs.getInt('counterLogin') ?? 0;
firstMobile = prefs.getInt('counterMobile') ?? 0;
firstOtp = prefs.getInt('counterOtp') ?? 0;
firstInfo = prefs.getInt('counterInfo') ?? 0;
prefs.setInt('counterLogin', firstLogin + 1);
prefs.setInt('counterMobile', firstMobile + 1);
prefs.setInt('counterOtp', firstOtp + 1);
prefs.setInt('counterInfo', firstInfo + 1);
if ((firstLogin == 0) && (firstMobile == 0) && (firstOtp == 0) && (firstInfo == 0)) {
setState(() {
print("first launch");
Navigator.of(context).pushNamed(LoginScreen.routeName);
});
} else {
setState(() {
print("not first launch");
Navigator.of(context).pushNamed(LandingSection.routeName);
});
}
}
And calling the setValue() in initState()
I am looking forward for solution.

Related

Riverpod with hooks beaks when widget is disposedI

I have Flutter mobile app that is using Riverpod with hooks.
I have the following function that I would like to be called when the widget is disposed:
useEffect(
() {
final firestoreRepo =
ref.read(firebaseFirestoreRepositoryProvider);
return () async {
try {
// I get exception at this line.
// I need this future to be called when the
// widget is disposed.
// Calling this future earlier is not userful
// for my business logic.
final relationship =
await ref.read(relationshipWithProvider(pid).future);
if (relationship?.unseen ?? false) {
await firestoreRepo?.updateRelatinoship(pid: pid);
}
} catch (e, st) {
// print error
}
};
},
[],
);
I keep getting this error at the line shown in the comment above.
I/flutter ( 5967): Looking up a deactivated widget's ancestor is unsafe.
I/flutter ( 5967): At this point the state of the widget's element tree is no longer stable.
How can I sold this problem
We can initially get our relationship and then await and use it:
useEffect(
() {
final firestoreRepo = ref.read(firebaseFirestoreRepositoryProvider);
final relationship = ref.read(relationshipWithProvider(pid).future);
return () async {
try {
if (await relationship?.unseen ?? false) {
await firestoreRepo?.updateRelatinoship(pid: pid);
}
} catch (e, st) {
// print error
}
};
},
[],
);
As far as I can tell, this won't contradict the logic of the business process, because one way or another, we'll have to make the relationshipWithProvider(pid) request early (when we initialize the widget) or late (when we delete the widget).

Flutter connectivity: Works on Android, but on iOS simulator when I can open webpages in Safari I have internet but the app says there is no internet?

I use this package: https://pub.dev/packages/connectivity_plus
I have a finished application that is working on Android but when I am testing it on iOS it shows that there is no internet. I can use and open pages in Safari so there is definitely one. But the following code returns false in iOS:
class InternetConnectivity with ChangeNotifier {
StreamSubscription<ConnectivityResult>? _subscription;
bool haveInternet = false;
void checkConnectivity() {
if (_subscription == null) {
_subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
bool res = result == ConnectivityResult.mobile || result == ConnectivityResult.wifi;
setHaveInternet = res;
});
}
}
set setHaveInternet(bool value) {
if (haveInternet != value) {
haveInternet = value;
notifyListeners();
}
}
}
I don't get any errors so I don't really know where to look for the problem.
On the screen where it checks that internet connection starts with this:
bool _haveInternet = true;
then in initState() I set the value of it:
#override
void initState() {
super.initState();
InternetConnectivity ? _internetConnectivity = InternetConnectivity();
setState(() {
_haveInternet = _internetConnectivity!.haveInternet;
});
After the initState() ran, the _haveInternet becomes false, so the connectivity_plus package returns false while normally it should be true.
Thanks in advance.
The package has a bug. According to documentation it should only affect iOS simulator. https://github.com/fluttercommunity/plus_plugins/issues/479
From package comments:
/// On iOS, the connectivity status might not update when WiFi
/// status changes, this is a known issue that only affects simulators.
/// For details see https://github.com/fluttercommunity/plus_plugins/issues/479.

SetState call blocks access to field updated by stream

I am making a barcode scanning functionality. The camera feed is setting _currentImage.
// CameraView.dart
CameraImage _currentImage;
CameraImage takePhoto() => _currentImage;
void imageStream(CameraImage image) async {
print("_currentImage is set with " + image.toString()); // keeps saying it sets 'currentImage'
this._currentImage = image;
}
Later on I call takePhoto from button pressed handler and call 'setState' empty...
void pressedShowBarcodeOnScreenButton() async {
var image = this.cameraView.takePhoto();
if (image == null) {
print(// occurs after having called empty 'setState' every time button is pressed
"image was null, maybe it is not possible to take images this fast!");
return;
}
var barcode =
await VisionService.getInstance() // is not producing any errors
.analyzeBarcode(ImageUtils.toAbstractImage(image));
if (Utils.isNullOrEmpty(barcode)) {
return;
}
print("got barcode: " + barcode); // prints all barcodes ok, but stops doing so when calling 'setState'
setState(() {
// Makes it so that 'currentImage' is null at all times, despite continuously set by imageStream given from log
//this.scannedBarcodes = [...this.scannedBarcodes, barcode]; // my intention eventual to update a list
});
}
I suspect it might be due to some underlaying dart behavour, or there is some sort of bug? Why does the setState call prevent me from accessing the _currentImage - or why is blocked from being set by the imageStream()?
The CameraView which contained the CameraController that had the stream, was part of the page to display the barcodes.
So SetState(){} triggered a reconstruction of the CameraView, and the controller was not able to be reconstructed. Probably the old stream was producing the log and was misleading.
It helped to extract the CameraController in this scenario, and have it accessed as singleton, inside CameraViewController that I made:
//...
static CameraController _cameraController;
static Future<CameraController> getCameraControllerInstance() async {
if (_cameraController == null) {
var cameras = await availableCameras();
if (cameras?.length == 0) {
print("the device did not have a camera");
return null;
}
_cameraController = CameraController(cameras[0], ResolutionPreset.max);
await _cameraController.initialize();
_cameraController.startImageStream(updateCurrentImage);
}
return _cameraController;
}
And I use it in a FutureBuilder like so, in the CameraView:
FutureBuilder futureBuilder() => FutureBuilder<CameraController>(
future: CameraViewController.getCameraControllerInstance(), // <--!!!
builder:
(BuildContext context, AsyncSnapshot<CameraController> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return LoadingPage();
}
return content(snapshot.data);
});
I am tempted to have one instance of the CameraView around at all times (?), since it looks glitchy when updating the list - but as I see it would be anti pattern as the Widget's are supposed to rerender (immutable) themselves:
The content of the page that has the barcode list:
Widget content() {
return Container(
child: (Column(children: [
cameraView, // camera view part of page and recontructed on 'scannedProducts' state change
header(),
scanButton(),
Column(
children: this.scannedProducts(), /// list
)
])));
}

How to "await" non-future variable?

I have DocumentReference locationDocumentRef; in my state.
I'm changing locationDocumentRef based on the references, whether I gather by querying or by adding new document.
So I have this function to check the documents, if there is one set its reference to the locationDocumentRef, or add a new one and set its ref to the locationDocumentRef. I'm resetting its value everytime by setting it to null, since I didn't want to get previous result. But it prints null.
So my question is, how can I resolve them and get the value? I think I'm resolving too early in my code, so I can't await a non-future value. How can I fix it?
void firestoreCheckAndPush() async {
setState(() {
locationDocumentRef = null;
});
bool nameExists = await doesNameAlreadyExist(placeDetail.name);
if (nameExists) {
print('name exist');
} else {
print('name will be pushed on firestore');
pushNameToFirestore(placeDetail);
}
var resolvedRef = await locationDocumentRef;
print(resolvedRef.documentID); // I get null here
}
These are the functions that I have used
Future<bool> doesNameAlreadyExist(String name) async {
QuerySnapshot queryDb = await Firestore.instance
.collection('locations')
.where("city", isEqualTo: '${name}')
.limit(1)
.getDocuments();
if (queryDb.documents.length == 1) {
setState(() {
locationDocumentRef = queryDb.documents[0].reference;
});
return true;
} else {
return false;
}
}
And the other
void pushNameToFirestore(PlaceDetails pd) async {
DocumentReference justAddedRef =
await Firestore.instance.collection('locations').add(<String, String>{
'city': '${pd.name}',
'image': '${buildPhotoURL(pd.photos[0].photoReference)}',
});
setState(() {
locationDocumentRef = justAddedRef;
});
}
there is two mistakes i saw first here
var resolvedRef = await locationDocumentRef;
why you await for locationDocumentRef,
second you dont wait for pushNameToFirestore(PlaceDetails pd) firestoreCheckAndPush() function which is weird since pushNameToFirestore(String) is sync and this means you wouldnt wait for it to finish so if you are adding a new name it would print null.
correct me if i am wrong.
you can find more about sync and future here https://www.dartlang.org/tutorials/language/futures
look at the graph at the middle of the page
Try this
Future<List<DocumentSnapshot>> doesNameAlreadyExist(String name) async {
QuerySnapshot data = await Firestore.instance
.collection('locations')
.where("city", isEqualTo: name)
.limit(1)
.getDocuments();
return data.documents;
}
void firestoreCheckAndPush() async {
var data = await doesNameAlreadyExist('yourname');
if (data.length > 0) {
print('name exist');;
print('Document id '+ data[0].documentID);
} else {
print('name will be pushed on firestore');
}
}
Take a look into following code.
void firestoreCheckAndPush() async {
DocumentReference documentReference;
var data = await doesNameAlreadyExist('yourname');
var dataRef = await doesNameAlreadyExist('yourname');
if (data.length > 0) {
print('name exist');
documentReference = dataRef[0].reference;
print('Document id ' + data[0].documentID);
documentReference = dataRef[0].reference;
print('Document reference ');
print(documentReference);
} else {
print('name will be pushed on firestore');
}
}

Execute Futures until a parameter becomes true

I launch a request to a server with a future "requestServer".
I would like to poll a system for a specific value (passed from false to true, when request is done) and return when finished.
Code could be like that, but "while" synchronous and "checkOperation" is asynchronous?
return requestServer().then((operation) {
var done = false;
while (done)
return checkOperation(operation).then((result) {
done = (result == true);
});
sleep(10);
}
});
Any ideas ?
I guess this is not exactly what you want but as far as I know there is no way to block execution so you have to use callbacks.
void main(List<String> args) {
// polling
new Timer.periodic(new Duration(microseconds: 100), (t) {
if(isDone) {
t.cancel();
someCallback();
}
});
// set isDone to true sometimes in the future
new Future.delayed(new Duration(seconds: 10), () => isDone = true);
}
bool isDone = false;
void someCallback() {
print('isDone: $isDone');
// continue processing
}
You can of course pass the callback as parameter instead of hardcode it, because functions are first class members in Dart.
Polling doesn't work very well for async. It is better to wait for a signal from the thing that must complete.
Günter Zöchbauer's answer shows you how to poll anyway, by sampling with a timer.
As an alternative, it would be better to not have a boolean done, but instead complete another future when you are ready. This is busy-polling, which polls again as soon as a result comes back, which may be more intensive than you need. Using timer based polling can be more efficient if you don't need the result as soon as possible.
return requestServer().then((operation) {
var completer = new Completer();
void poll(result) {
if (!result) {
operation.then(poll, onError: completer.completeError);
} else {
completer.complete();
}
}
poll(false);
return completer.future;
});
(Code not really tested, since I don't have your requestServer).
When you want build functions that return Futures, it is sometimes useful to use Completers. Think that requestServer() is living in the Future too, so you will have threat the result as a Future.
return requestServer().then((operation) {
// This is necessary then you want to control async
// funcions.
Completer completer = new Completer();
//
new Timer.periodic(const Duration(seconds: 10), (_) {
checkOperation(operation).then((result) {
// Only when the result is true, you pass the signal
// that the operation has finished.
// You can alse use `completer.complete(result)` if you want
// to pass data inside of the future.
if (result == true) completer.complete();
});
});
// You return the future straight away.
// It will be returned by requestServer();
return completer.future;
});
I use a function like this in a TestUtil library:
static Future<bool> waitUntilTrue(bool Function() callback,
{Duration timeout: const Duration(seconds: 2),
Duration pollInterval: const Duration(milliseconds: 50)}) {
var completer = new Completer<bool>();
var started = DateTime.now();
poll() {
var now = DateTime.now();
if (now.difference(started) >= timeout) {
completer.completeError(Exception('timed out in waitUntilTrue'));
return;
}
if (callback()) {
completer.complete(true);
} else {
new Timer(Duration(milliseconds: 100), () {
poll();
});
}
}
poll();
return completer.future;
}
And then in my test code I'll do something like:
await TestUtil.waitUntilTrue(() => someObj.isDone);
Edit:
Note that if you're using this in a testWidgets test, you have to do a little extra, since it relies on real async work happening:
await tester.runAsync<bool>(
() => TestUtil.waitUntilTrue(() => myObj.isLoaded),
additionalTime: Duration(seconds: 5));

Resources