How to write widget test for this widget - flutter

My Widget of which i want to write widget test
InkWell(
key: const Key(kDraftTileKey),
onTap: () {
context.read<CreatorHubTapViewModel>().startPublishingFlowAgain(startPublishingFlowAgainPressed: startPublishingFlowAgainPressed);
},
child:...
);
Fucntion which called when user click on a tap
void startPublishingFlowAgainPressed() {
widget.viewModel.saveNFT(nft: widget.nft);
Navigator.of(context).pushNamed(RouteUtil.kRouteHome);
}
I create a Mock of this viewmodel using Mockito
class CreatorHubTapViewModel extends ChangeNotifier {
void onViewOnPylons({required VoidCallback onViewOnPylonsPressed}) {
onViewOnPylonsPressed.call();
}
void startPublishingFlowAgain({required VoidCallback startPublishingFlowAgainPressed}) {
startPublishingFlowAgainPressed.call();
}
}
Now in test when i try to make a stub on this function it is not calling thenAnswer also not calling Callback itself. Any way to achieve i just want to toggle my variable when this function calls in actual screen.
when(viewModel.startPublishingFlowAgain(startPublishingFlowAgainPressed: () {
clicked = true;
})).thenAnswer((realInvocation) {});

This is how you can test this function.
when(viewModel.startPublishingFlowAgain(startPublishingFlowAgainPressed: anyNamed("startPublishingFlowAgainPressed"))).thenAnswer((realInvocation) {
clicked = true;
});

Related

Widget not updating flutter

I'm trying to change the variable from another stateful class.
class first extends statefulwidget {
bool text = false;
Widget build(BuildContext context) {
setState((){});
return Container(
child: text ? Text('Hello') : Text('check')
);
}
}
class second extends statefulwidget {
Widget build(BuildContext context) {
return Container(
child: IconButton(
onPressed: () {
first fir = first();
setState((){
fir.test = true;
});
}
)
);
}
}
widget shows only check not showing Hello
This is my code...Ignore spelling mistakes and camelcase
Give me the solutions if you know..
If you are trying to access data on multiple screens, the Provider package could help you. It stores global data accessible from all classes, without the need of creating constructors. It's good for big apps.
Here are some steps to use it (there is also a lot of info online):
Import provider in pubspec.yaml
Create your provider.dart file. For example:
class HeroInfo with ChangeNotifier{
String _hero = 'Ironman'
get hero {
return _hero;
}
set hero (String heroName) {
_hero = heroName;
notifyListeners();
}
}
Wrap your MaterialApp (probably on main.dart) with ChangeNotifierProvider.
return ChangeNotifierProvider(
builder: (context) => HeroInfo(),
child: MaterialApp(...),
);
Use it on your application! Call the provider inside any build method and get data:
#override
Widget build(BuildContext context){
final heroProvider = Provider.of<HeroInfo>(context);
return Column {
children: [
Text(heroProvider.hero)
]
}
}
Or set data:
heroProvider.hero = 'Superman';
try to reference to this answer, create function to set boolean in class1 and pass as parameter to class 2 and execute it :
typedef void MyCallback(int foo);
class MyClass {
void doSomething(int i){
}
MyOtherClass myOtherClass = new MyOtherClass(doSomething);
}
class MyOtherClass {
final MyCallback callback;
MyOtherClass(this.callback);
}

How to trigger a function in a stateful widget from another class (when onUnityAdsFinish)?

I have a game screen in a stateful widget that includes a function that lets the player take a step back (undo the previous move). I would like to trigger this function when onUnityAdsFinish() runs from another widget.
Here's the stateful widget on game.dart where the game is played:
class GameScreen extends StatefulWidget {
#override
GameScreenState createState() => GameScreenState();
}
class GameScreenState extends State<GameScreen> with SingleTickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Container(
// game content here
),
MaterialButton(
child: Text("Tap Here to Watch Ad to Undo Move"),
onPressed: () => unityBloc.showUnityAd(),
// (side note: unityBloc is accessable because I have it declared as "final unityBloc = UnityBloc();" higher up the widget tree on app.dart.)
),
],
),
}
// This is the function I want to call after the ad is finished.
void undoMove() {
setState(() {
// code to undo move
});
}
}
When the button is pressed to watch the ad, it calls the showUnityAd() function in my UnityBloc class on unity_bloc.dart:
import 'package:unity_ads_flutter/unity_ads_flutter.dart';
class UnityBloc with UnityAdsListener {
...
showUnityAd() {
UnityAdsFlutter.show('rewardedVideo').whenComplete(() => null);
} // called when undo button is pressed on game.dart
#override
void onUnityAdsFinish(String placementId, FinishState result) {
print("Finished $placementId with $result");
// here is where I want to call the undoMove() function from game.dart
}
}
The ad displays perfectly and the print statement in onUnityAdsFinish prints to the console when the ad is finished, but I can't figure out how to call undoMove at that point.
I have spent all day watching tutorials and reading stackoverflows about callbacks and global keys and such, but I'm just still not getting it.
Can someone please help? How can I trigger the undoMove() function on game.dart when onUnityAdsFinish() fires on unity_bloc.dart? (Or is their another/better way to do this?)
Thank you!
You can have a variable that holds a function in the UnityBloc class and pass the undoMove method to the showUnityAd method to assign it to the variable and then you can call it in onUnityAdsFinish method.
So UnityBloc becomes this:
class UnityBloc with UnityAdsListener {
void Function() doOnUnityAdsFinish;
...
showUnityAd({#required unityAdsFinishFunction}) {
...
doOnUnityAdsFinish = unityAdsFinishFunction;
} // called when undo button is pressed on game.dart
#override
void onUnityAdsFinish(String placementId, FinishState result) {
...
doOnUnityAdsFinish?.call(); //Only calls if the function is not null
}
}
And you can it in the MaterialButton's onPressed like this:
MaterialButton(
child: Text("Tap Here to Watch Ad to Undo Move"),
onPressed: () => unityBloc.showUnityAd(unityAdsFinishFunction: undoMove ),
),
Simplest approach would be a global/static variable (callback) or a ValueNotifier/Stream.
Maybe, a cleaner approach, would be some package with dependency injection capabilities like (get, get_it).
class UnityBloc with UnityAdsListener {
/// pass a callback into the constructor.
/// Function<String,FinishState> onAdCompleted ;
/// UnityBloc([this.onAdCompleted]);
/// easier approach, not very "flexible".
static Function<String,FinishState> onAdCompleted ;
showUnityAd() {
UnityAdsFlutter.show('rewardedVideo').whenComplete(() => null);
} // called when undo button is pressed on game.dart
#override
void onUnityAdsFinish(String placementId, FinishState result) {
onAdCompleted?.call(placementId, result);
// here is where I want to call the undoMove() function from game.dart
}
}
class GameScreenState extends State<GameScreen> with SingleTickerProviderStateMixin {
#override
void initState(){
super.initState();
UnityBloc.onAdCompleted = _onAddCompleted;
}
#override
void dispose(){
super.dispose();
if( UnityBloc.onAdCompleted == _onAddCompleted) {
UnityBloc.onAdCompleted = null;
}
}
void _onAddCompleted(String placementId, FinishState result)
=> print("Finished $placementId with $result");
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Container(
// game content here
),
MaterialButton(
child: Text("Tap Here to Watch Ad to Undo Move"),
onPressed: () => unityBloc.showUnityAd(),
// (side note: unityBloc is accessable because I have it declared as "final unityBloc = UnityBloc();" higher up the widget tree on app.dart.)
),
],
),
}
// This is the function I want to call after the ad is finished.
void undoMove() {
setState(() {
// code to undo move
});
}
}

Flutter setState not updating child element

I have an InkWell which uses onTap to perform some actions. When the button is tapped, I like an indicator to be shown (in case the action is long-running). However, the setState in the InkWell does not trigger its children to be re-rendered. The code is as follows:
class PrimaryButtonState extends State<PrimaryButton> {
bool _apiCall;
Widget getWidget() {
if(_apiCall) {
return new CircularProgressIndicator();
} else {
return Text(
widget.label,
);
}
}
#override
Widget build(BuildContext context) {
final List<Color> colors = //omitted
return InkWell(
child: Container(
decoration: // omitted
child: getWidget(), // not updated when _apiCall changes !!!!!
),
onTap: () {
setState(() {
_apiCall = true;
});
widget.onTab(context);
setState(() {
_apiCall = false;
});
}
);
}
}
How can I solve this that getWidget returns the correct widget dependent on _apiCall?
EDIT:
The widget.onTap contains the following:
void performLogin(BuildContext context) {
final String userName = _userName.text.trim();
final String password = _password.text.trim();
UserService.get().loginUser(userName, password).then((val) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => MainLayout()));
}).catchError((e) {
// omitted
});
}
it is passed with the widget:
class PrimaryButton extends StatefulWidget {
final bool isPrimary;
final String label;
final Function(BuildContext context) onTab;
PrimaryButton(this.label, this.isPrimary, this.onTab);
#override
State<StatefulWidget> createState() => PrimaryButtonState();
}
My main concern is, that the given onTap method should not know it is "bound" to a UI widget and therefore should not setState. Also, as this is a general button implementation I like it to be interchangeable (therefore, onTap is not hardcoded)
It looks like your problem is because you are calling setState() twice in your onTap() function. Since onTap() is not an async function it will set _apiCall = true in the first setState, then immediately run widget.onTab(context) and then immediately perform the second setState() to set _apiCall = false so you never see the loading widget.
To fix this you will need to make your onTab function an async function and await for a value in your onTap function for your InkWell:
onTap: () async {
setState(() {
_apiCall = true;
});
await widget.onTab(context);
setState(() {
_apiCall = false;
});
}
This will also let you use the results of your onTab function to show errors or other functionality if needed.
If you are unsure how to use async functions and futures here is a good guide on it that goes over this exact kind of use case.

How to go back and refresh the previous page in Flutter?

I have a home page which when clicked takes me to another page through navigates, do some operations in then press the back button which takes me back to the home page. but the problem is the home page doesn't get refreshed.
Is there a way to reload the page when i press the back button and refreshes the home page?
You can trigger the API call when you navigate back to the first page like this pseudo-code
class PageOne extends StatefulWidget {
#override
_PageOneState createState() => new _PageOneState();
}
class _PageOneState extends State<PageOne> {
_getRequests()async{
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new RaisedButton(onPressed: ()=>
Navigator.of(context).push(new MaterialPageRoute(builder: (_)=>new PageTwo()),)
.then((val)=>val?_getRequests():null),
),
));
}
}
class PageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
//somewhere
Navigator.pop(context,true);
}
}
Or you can just use a stream if the API is frequently updated, the new data will be automatically updated inside your ListView
For example with firebase we can do this
stream: FirebaseDatabase.instance.reference().child(
"profiles").onValue
And anytime you change something in the database (from edit profile page for example), it will reflect on your profile page. In this case, this is only possible because I am using onValue which will keep listening for any changes and do the update on your behalf.
(In your 1st page): Use this code to navigate to the 2nd page.
Navigator.pushNamed(context, '/page2').then((_) {
// This block runs when you have returned back to the 1st Page from 2nd.
setState(() {
// Call setState to refresh the page.
});
});
(In your 2nd page): Use this code to return back to the 1st page.
Navigator.pop(context);
use result when you navigate back from nextScreen as follow :
Navigator.of(context).pop('result');
or if you are using Getx
Get.back(result: 'hello');
and to reload previous page use this function :
void _navigateAndRefresh(BuildContext context) async {
final result = await Get.to(()=>NextScreen());//or use default navigation
if(result != null){
model.getEMR(''); // call your own function here to refresh screen
}
}
call this function instead of direct navigation to nextScreen
The solution which I found is simply navigating to the previous page:
In getx:
return WillPopScope(
onWillPop: () {
Get.off(() => const PreviousPage());
return Future.value(true);
},
child: YourChildWidget(),
or if you want to use simple navigation then:
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) =>PreviousPage() ,));
Simply i use this:
onPressed: () {
Navigator.pop(context,
MaterialPageRoute(builder: (context) => SecondPage()));
},
this to close current page:
Navigator.pop
to navigate previous page:
MaterialPageRoute(builder: (context) => SecondPage())
In FirtsPage, me adding this for refresh on startUpPage:
#override
void initState() {
//refresh the page here
super.initState();
}
For a more fine-grained, page-agnostic solution I came up with this Android Single LiveEvent mimicked behaviour.
I create such field inside Provider class, like:
SingleLiveEvent<int> currentYearConsumable = SingleLiveEvent<int>();
It has a public setter to set value. Public consume lets you read value only once if present (request UI refresh). Call consume where you need (like in build method).
You don't need Provider for it, you can use another solution to pass it.
Implementation:
/// Useful for page to page communication
/// Mimics Android SingleLiveEvent behaviour
/// https://stackoverflow.com/questions/51781176/is-singleliveevent-actually-part-of-the-android-architecture-components-library
class SingleLiveEvent<T> {
late T _value;
bool _consumed = true;
set(T val) {
_value = val;
_consumed = false;
}
T? consume() {
if (_consumed) {
return null;
} else {
_consumed = true;
return _value;
}
}
}
await the navigation and then call the api function.
await Navigator.of(context).pop();
await api call
You can do this with a simple callBack that is invoked when you pop the route. In the below code sample, it is called when you pop the route.
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
_someFunction()async{
Navigator.of(context).push(MaterialPageRoute(builder: (_)=> PageTwo(
onClose():(){
// Call setState here to rebuild this widget
// or some function to refresh data on this page.
}
)));
}
#override
Widget build(BuildContext context) {
return SomeWidget();
}
...
} // end of widget
class PageTwo extends StatelessWidget {
final VoidCallback? onClose;
PageTwo({Key? key, this.onClose}) : super(key: key);
#override
Widget build(BuildContext context) {
return SomeWidget(
onEvent():{
Navigate.of(context).pop();
onClose(); // call this wherever you are popping the route
);
}
}

setState() called after dispose()

When I click the raised button, the timepicker is showing up. Now, if I wait 5 seconds, for example, and then confirm the time, this error will occur:
setState() called after dispose()
I literally see in the console how flutter is updating the parent widgets, but why? I don't do anything - I just wait 5 seconds?!
The example below will work in a normal project, however in my project which is quite more complex it won't work because Flutter is updating the states while I am waiting... What am I doing wrong? Does anyone have a guess at what it could be that Flutter is updating randomly in my more complex project and not in a simple project?
[UPDATE]
I took a second look at it and found out it is updating from the level on where my TabBar and TabBarView are.
Could it have to do something with the "with TickerProviderStateMixin" which I need for the TabBarView? Could it be that it causes the app to refresh regularly and randomly?
class DateTimeButton extends State<DateTimeButtonWidget> {
DateTime selectedDate = new DateTime.now();
Future initTimePicker() async {
final TimeOfDay picked = await showTimePicker(
context: context,
initialTime: new TimeOfDay(hour: selectedDate.hour, minute: selectedDate.minute),
);
if (picked != null) {
setState(() {
selectedDate = new DateTime(selectedDate.year, selectedDate.month, selectedDate.day, picked.hour, picked.minute);
});
}
}
#override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text("${selectedDate.hour} ${selectedDate.minute}"),
onPressed: () {
initTimePicker();
}
);
}
}
Just check boolean property mounted of the state class of your widget before calling setState().
if (this.mounted) {
setState(() {
// Your state change code goes here
});
}
Or even more clean approach
Override setState method in your StatelfulWidget class.
class DateTimeButton extends StatefulWidget {
#override
void setState(fn) {
if(mounted) {
super.setState(fn);
}
}
}
If it is an expected behavior that the Future completes when the widget already got disposed you can use
if (mounted) {
setState(() {
selectedDate = new DateTime(selectedDate.year, selectedDate.month, selectedDate.day, picked.hour, picked.minute);
});
}
Just write one line before setState()
if (!mounted) return;
and then
setState(() {
//Your code
});
I had the same problem and i solved changing the super constructor call order on initState():
Wrong code:
#override
void initState() {
foo_bar(); // call setState();
super.initState(); // then foo_bar()
}
Right code:
#override
void initState() {
super.initState();
foo_bar(); // first call super constructor then foo_bar that contains setState() call
}
To prevent the error from occurring, one can make use of the mounted property of the State class to ensure that a widget is mounted before settings its state:
// First Update data
if (!mounted) {
return;
}
setState(() { }
Try this
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text("${selectedDate.hour} ${selectedDate.minute}"),
onPressed: () async {
await initTimePicker();
}
);
}
class MountedState<T extends StatefulWidget> extends State<T> {
#override
Widget build(BuildContext context) {
return null;
}
#override
void setState(VoidCallback fn) {
if (mounted) {
super.setState(fn);
}
}
}
Example
To prevent the error,Instead of using State use MountedState
class ExampleStatefulWidget extends StatefulWidget {
const ExampleStatefulWidget({Key key}) : super(key: key);
#override
_ExampleStatefulWidgetState createState() => _ExampleStatefulWidgetState();
}
class _ExampleStatefulWidgetState extends MountedState<ExampleStatefulWidget> {
....
}
I had this error when I mistakenly called super.initState before the variable. Check this:
#override
void initState() {
super.initState();
bloc = MainBloc();
}
Should be fixed as
#override
void initState() {
bloc = MainBloc();
super.initState();
}
The problem could occur when you have long asynchronous operation in stateful widget that could be closed/disposed before the operation finished.
Futures in Dart are not preemptive, so the only way is to check if a widget mounted before calling setState.
If you have a lot of widgets with asynchrony, adding ton of if (mounted) checks is tedious and an extension method might be useful
extension FlutterStateExt<T extends StatefulWidget> on State<T> {
void setStateIfMounted(VoidCallback fn) {
if (mounted) {
// ignore: invalid_use_of_protected_member
setState(fn);
}
}
}