How to test widget that is instantiated in didUpdateWidget() in Flutter? - asynchronous

I have a StatefulWidget that creates an AnimatedCrossFade widget in didUpdateWidget(), and saves it as animation. Here's a pared down example:
class BackgroundImage extends StatefulWidget {
final Color colorA;
final Color colorB;
const BackgroundImage({
this.colorA,
this.colorB,
});
}
class _BackgroundImageState extends State<BackgroundImage> {
Widget _animation;
#override
void didUpdateWidget(Widget old) {
super.didUpdateWidget(old);
_buildBackgroundA(colorA).then((backgroundA) {
_buildBackgroundB(colorB).then(backgroundB) {
print(backgroundA); // this is not null
print(backgroundB); // this is not null
_animation = AnimatedCrossFade(
duration: Duration(seconds: 15),
firstChild: backgroundA,
secondChild: backgroundB,
crossFadeState: someVarToSwitchColors,
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
);
}
}
}
#override
Widget build(BuildContext context) {
return _animation != null ? _animation : Container();
}
}
_buildBackgroundA() and _buildBackgroundB() are async functions that reuturn Future<Widget>. This works fine in my app - didUpdateWidget() is called, my AnimatedCrossFade shows up and animates between the two backgrounds.
However, I'm having trouble finding AnimatedCrossFade in my test. I'm able to find my other Stateless widgets, and also able to find the BackgroundImage widget. I have something like:
await tester.pump();
await tester.pumpAndSettle(Duration(minutes: 1));
expect(
find.descendant(
of: find.byType(BackgroundImage),
matching: find.byType(AnimatedCrossFade)),
findsOneWidget);
This fails as it can't find AnimatedCrossFade.
If I change my build() function to:
#override
Widget build(BuildContext context) {
return AnimatedCrossFade(...);
}
I'm able to find my widget. So I suspect it has something to do with my test expect executing before my _buildBackground() functions are done. I've tried altering the duration in my pump and pumpAndSettle to no effect. How do I force the test to wait more? Is there something else I'm missing?
The test log looks like this (with my prints):
Running Flutter tests.
00:00 +0: ...d/work/.../cl00:00 +0: (setUpAll) 00:00 +0: Test background
init state called
_buildBackgroundA called
init state called
_buildBackgroundB called
...
00:00 +0 -1: Test background
Expected: exactly one matching node in the widget tree
Actual: ?:<zero widgets with type "AnimatedCrossFade" that has ancestor(s) with type "BackgroundImage" (ignoring offstage widgets)>
Which: means none were found but one was expected
This was caught by the test expectation on the following line:
...line 179
about to return from calling _buildBackgroundA
image: Image(image: MemoryImage(_Uint8ArrayView#af55b, scale: 1.0), width: 50.0, height: 200.0, fit: cover, alignment: center, this.excludeFromSemantics: false, filterQuality: low)
about to return from calling _buildBackgroundB
image: Image(image: MemoryImage(_Uint8ArrayView#79291, scale: 1.0), width: 50.0, height: 830.0, fit: cover, alignment: center, this.excludeFromSemantics: false, filterQuality: low)
...

Related

flutter_map: how to make a Marker active

My task is to allow the user to build a polygon on the map. I use the flutter_map library and openstreetmaps. I do not know how to complete the building of the polygon.
Now, by the very first onTap, an IconButton appears on the map. I want to make this IconButton active so that when the user onPress on it: (1) the icon changes, (2) returns (and saves for passing to the backend) a set of points that the user tapped during the polygon building process, and (3) the polygon building process stops (adding new points became impossible).
I think, the challenge comes down to how callback setState inside another setState while retaining functionality. But maybe there is another solution.
My code is below. I can provide the whole code if needed.
I would appreciate any ideas.
class _HomePageState extends State<HomePage> {
...
List<Marker> markers = [];
List<LatLng> polygonList = [];
bool singleTap = false;
...
options: MapOptions(
...
onTap: (latlng) {
if (singleTap) {
setState(() {
if (markers.length == 0) {
markers.add(
Marker(
point: latlng,
builder: (ctx) => IconButton(
icon: Icon(Icons.favorite),
onPressed: () {***HERE the process should be stopped with changing the icon, no new points;***
setState(() {});
},
),
),
);
} else {
markers.add(
Marker(...),
),
);
}
polygonList.add(latlng);
});
}
}),
An answer is very simple: nothing special to do with it, just keep in mind that "other layers may intercept the gestures". I just changed the order of layers, and now my Marker is active.

how can i check if my sqlflite database has data while starting the app in flutter?

I am doing a project where I have to keep an favorite icon and a selected fvrt list... Now using sqlflite .. I have done it.. when the user presses the favorite border icon it get changed to red color and the data saves in the favorite list.. when user pressses again in the same button .. the data gets delated from the list and the favorite button change to ist default color... but what i am not able to do is.. the favorite button is default false.. so even if the data is collected in the fvrt list .. all the fvrt button shows _fvrt default favorite btn when i start the app ...
i was wondering how can i check the data in the initState() , if the data already exit in database it fvrt btn will remain red..
here's a little code of the conditon that i haved used .
Widget _buildRow(String pair) {
final bool alreadySaved = _saved.contains(pair);
print("Already saved $alreadySaved");
print(pair);
return IconButton(
icon: new Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color:alreadySaved? Colors.red : Colors.white,
),onPressed: (){
setState(() {
if (alreadySaved) {
_saved.remove(pair);
_deleteEmployee(pair);
} else {
_saved.add(pair);
_insert(pair);
}
});
},
);
}
Reading data from your database is an async function - it takes some time. What you can do, is to create a loading state, and show a loading indicator, until the async function finishes.
import 'package:flutter/material.dart';
class MyClass extends StatefulWidget {
#override
_MyClassState createState() => _MyClassState();
}
class _MyClassState extends State<MyClass> {
bool isLoading = false;
List _saved = [];
#override
void initState() {
// Note that you cannot use `async await` in initState
isLoading = true;
_readFromDataBase().then((savedStuff) {
_saved = savedStuff;
isLoading = false;
});
super.initState();
}
#override
Widget build(BuildContext context) {
return !isLoading ? _buildRow("myPair") : CircularProgressIndicator();
}
Widget _buildRow(String pair) {
final bool alreadySaved = _saved.contains(pair);
print("Already saved $alreadySaved");
print(pair);
return IconButton(
icon: new Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color:alreadySaved? Colors.red : Colors.white,
),onPressed: (){
setState(() {
if (alreadySaved) {
_saved.remove(pair);
_deleteEmployee(pair);
} else {
_saved.add(pair);
_insert(pair);
}
});
},
);
}
}
Alternatively you can check the FutureBuilder Widget. Here is the official documentation: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html

Is there a way to save canvas drawing in flutter to firebase other than converting it to image

i am creating a flutter app where the user has a canvas which he can draw on and then he can save the canvas to Firestore and can recall and edit whenever he wants.I have seen tutorials in creating a canvas and drawing on it but i dont know how to save it to firebase, i have seen some say to convert it as an image and save it to firebase storage but after saving it as an image can the user recall and edit it , and is it possible to save all the points the user has drawn on canvas in the form of a list
Below is a code i am working on,
In this i am trying to save all the points in a list and update it to firebase
class _HomePageState extends State<HomePage> {
List<Offset> _points = <Offset>[];
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Container(
child: new GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
setState(() {
RenderBox object = context.findRenderObject();
Offset _localPosition =
object.globalToLocal(details.globalPosition);
_points = new List.from(_points)..add(_localPosition);
});
},
onPanEnd: (DragEndDetails details) => _points.add(null),
child: new CustomPaint(
painter: new Signature(points: _points),
size: Size.infinite,
),
),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.save),
onPressed: () => Firestore.instance.collection('points').document('XcX3MbUWt3hiBJyiPMIO'
).updateData({'points':FieldValue.arrayUnion(_points)})
),
);
}
}
class Signature extends CustomPainter {
List<Offset> points;
Signature({this.points});
#override
void paint(Canvas canvas, Size size) {
Paint paint = new Paint()
..color = Colors.blue
..strokeCap = StrokeCap.round
..strokeWidth = 10.0;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], paint);
}
}
}
#override
bool shouldRepaint(Signature oldDelegate) => oldDelegate.points != points;
}
It gives an error
Unhandled Exception: Invalid argument: Instance of 'Offset'
Is this method of saving possible of so how to do
I am able to print the offsets of the canvas

Banner ads are showing but Interstitial are not while test banner and test interstitial working perfectly

In my application I applied Both test ads (banner and interstitial) they are showing perfectly but when I apply real ads Both of them did not display.
I wait for 1 day and after one day only banner ads are showing now interstitial ads are not showing ( my ads id where generated 1 years before than why I need to wait 1 day to show in my application after uploading to google play store.
Now how can I display Interstitial ads please?
class Afcon extends StatefulWidget {
final String link;
Afcon({this.link});
#override
_AfconState createState() => _AfconState();
}
class _AfconState extends State<Afcon> {
void initState() {
super.initState();
FirebaseAdMob.instance.initialize(appId: AppId);
bannerAd = buildBanner()..load();
interstitialAd = buildInterstitial()..load();
}
#override
Widget build(BuildContext context) {
bannerAd ..load()..show(
anchorOffset: 20.0,
anchorType: AnchorType.bottom,
);
Future<bool> _onBackPressed() {
if(counter<1){
interstitialAd
..load()..show();
counter++;
}
else{
bannerAd.dispose();
Navigator.pop(context, true);
}
}
return WillPopScope(
child: WebviewScaffold(
appBar: AppBar(
title: Text('AFCON'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.open_in_new),
onPressed: (){
_newPage(widget.link);
}
)
],
),
initialChild: SpinKitWave(
color: Colors.black,
size: 30.0,
),
hidden: true,
url: widget.link,
),
onWillPop: _onBackPressed,
);
}
}
_newPage(String link) async {
if (await canLaunch(link)) {
await launch(link);
} else {
throw 'Could not launch $link';
}
}
Here initialized the Functions to show ads
import 'package:firebase_admob/firebase_admob.dart';
final AppId='ca-app-pub-**********************';
final InterstitialAdsUnit='ca-app-pub-**********************';
final BannerAdsUnit='ca-app-pub-**********************';
int counter=0;
final MobileAdTargetingInfo targetingInfo = MobileAdTargetingInfo(
keywords: ['Games', 'Puzzles'],
);
BannerAd bannerAd;
InterstitialAd interstitialAd;
RewardedVideoAd rewardedVideoAd;
BannerAd buildBanner() {
return BannerAd(
adUnitId: BannerAdsUnit,
size: AdSize.banner,
listener: (MobileAdEvent event) {
print(event);
});
}
InterstitialAd buildInterstitial() {
return InterstitialAd(
adUnitId: InterstitialAdsUnit,
targetingInfo: targetingInfo,
listener: (MobileAdEvent event) {
if (event == MobileAdEvent.failedToLoad) {
interstitialAd..load();
} else if (event == MobileAdEvent.closed) {
interstitialAd = buildInterstitial()..load();
}
print(event);
});
}
You have done everything perfectly from your side now you need to check the interstitial ads unit Id I am sure you have issue with ads unit ID. Go and make some new test Id and implement it on your app it will work. after applying new ads unit just wait sometime to let them show.
Never test your own ad, always use test ad units,
If you create a new admob app id, it might take some time for these ad units to go live, so it won't show ad.
So, if you run test ad units, does it work in real device?
Are you testing real ads on debug version of apk ? If yes,
Please try testing in real device with signed version of apk. I've seen many times real ads don't show in debug version of apk.

How to get data from Firestore in Scoped Model - Flutter

I'm trying to get data from Firestore, in debug print the future does it job and list gets data and in debugPrint length is +, but when I try to get data in another Widget list recives null, in debugPrint length is 0 .
model.dart
class BBModel extends Model {
int _counter = 10;
int get counter => _counter;
var db = dbBB;
List<BB> _bbs;
List<BB> get bbs => _bbs;
Future<List<BB>> getBBs() async {
var snapshot = await db.getDocuments();
for (int i = 0; i < snapshot.documents.length; i++) {
_bbs.add(BB.fromSnapshot(snapshot.documents[i]));
print(bbs.length.toString()); //recives 23
}
notifyListeners();
return _bbs;
}
}
main.dart
void main() {
var model = BBModel();
model.getBBs();
runApp(ScopedModel<BBModel>(model: BBModel(), child: MyApp()));
}
statefullpage.dart
Expanded(
flex: 1,
child: Container(
height: 400.0,
child: ScopedModelDescendant<BBModel>(
builder: (context, child, model) {
return ListView.builder(
itemCount: model.bbs.length,
itemBuilder: (context, index) {
return Text(model.bbs[index].bbID);
});
}))),
Looks like the code you're written in main.dart is wrong. The instatiated model is different from the one you've sent in your ScopedModel.
Correction
Change model: model to model: BBModel() in your main.dart file.
void main() {
final model = BBModel();
model.getBBs();
runApp(ScopedModel<BBModel>(model: model, child: MyApp()));
}
In main.dart, I would try doing:
void main() {
var model = BBModel();
model.getBBs().then((someVariableName){
runApp(ScopedModel<BBModel>(model: BBModel(), child: MyApp()));
});
}
note: "someVariableName" will contain a List< BB>
To wait you can use the
await model.getBBs();
Apart from this however, I do not recommend uploading data to the main, as you would slow down the use of the app, as the data is getting bigger. Upload the data only to the pages you need and find a way to do this.

Resources