How to check if widget is visible using FlutterDriver - flutter

I am not sure how I would check whether a widget is currently shown or not using FlutterDriver.
Using WidgetTester this is really easy to perform using e.g. findsOneWidget.
However, during integration tests using FlutterDriver, there is no access to a WidgetTester object.
The FlutterDriver.waitFor method does not indicate whether the widget was found in the given duration.
How do I check whether a widget is on screen using FlutterDriver?

Flutter Driver doesn't have explicit method to check if a widget is present / exists, but we can create a custom method which will serve the purpose using waitFor method. For example, I have a simple text widget on screen and I will write a flutter driver test to check if that widget is present or not using a custom method isPresent.
Main code:
body: Center(
child:
Text('This is Test', key: Key('textKey'))
Flutter driver test to check if this widget is present or not is as below:
test('check if text widget is present', () async {
final isExists = await isPresent(find.byValueKey('textKey'), driver);
if (isExists) {
print('widget is present');
} else {
print('widget is not present');
}
});
isPresent is custom method whose definition is as below:
isPresent(SerializableFinder byValueKey, FlutterDriver driver, {Duration timeout = const Duration(seconds: 1)}) async {
try {
await driver.waitFor(byValueKey,timeout: timeout);
return true;
} catch(exception) {
return false;
}
}
Running the test detects the widget is present:
If I comment out the text widget code and then run the test, it detects that widget is not present:

Related

How to change the default routing behavior when entering a new URL through address bar in a flutter web app? (using getx)

I am working on a Flutter Web App using Getx for navigation and state management. One of the routes in my flutter app has two query parameters. Let us call these parameters Dataset and Index. When the Dataset parameter is changed through the URL, I want to make an API call to retrieve the new dataset, and when the Index parameter is changed, I want to display the data from the dataset at that particular index on the app. Index in this case is an observable RxInt variable defined in the controller.
However, the default behavior when I change the URL and press enter is for the Flutter app to push a new page on to the navigation stack. The behavior I prefer is to simply update the values and make a new API call if necessary. The API call may be done by simply refreshing the page since it is handled by the Getx controller onInit function.
I'm not very familiar with how routing in flutter works and I haven't found a solution to change the behavior for routing itself. I've tried a few ways to update the values despite pushing the new page on to the stack, such as setting the value for index through the initState or build calls on my widgets but those changes aren't visible on my UI. I've also tried reinitializing the controller by deleting it but that didn't work either.
EDIT: I have added a code example:
Widget:
class MainscreenView extends StatefulWidget {
const MainscreenView({Key? key}) : super(key: key);
#override
State<MainscreenView> createState() => _MainscreenViewState();
}
class _MainscreenViewState extends State<MainscreenView> {
late MainscreenController mainscreenController;
#override
Widget build(BuildContext context) {
return GetX<MainscreenController>(
init: MainscreenController(),
initState: (_) {
mainscreenController = Get.find<MainscreenController>();
},
builder: (_) {
return Scaffold(
body: Center(
child: Text(
'Current index is ${mainscreenController.index.value}',
style: const TextStyle(fontSize: 20),
),
),
);
});
}
}
Controller:
class MainscreenController extends GetxController {
final index = 0.obs;
late String? dataset;
#override
void onInit() {
super.onInit();
final String? datasetQuery = Get.parameters['dataset'];
if (datasetQuery != null) {
dataset = datasetQuery; //API call goes here
} else {
throw Exception('Dataset is null');
}
final String? indexString = Get.parameters['index'];
if (indexString == null) {
throw Exception('Index is null');
} else {
final int? indexParsed = int.tryParse(indexString);
if (indexParsed == null) {
throw Exception('Index Cannot be parsed');
} else {
index.value = indexParsed;
}
}
}
}
The initial route is /mainscreen?dataset=datasetA&index=0. If I were to modify the route in the address bar to /mainscreen?dataset=datasetA&index=5 for example and press enter, The current behavior of Flutter is to push a new page onto the navigation stack. I would like to update the value of index instead and display it on the same page, but I haven't found a way to accomplish this. Also, if dataset parameter is updated I would like to again avoid pushing a new page onto the stack and refresh the current page instead so that the onInit function is run again and the API call is made automatically.

decide the route based on the initialization phase of flutter_native_splash

I'm writing a flutter application using flutter 2.10 and I'm debugging it using an Android Emulator
I included the flutter_native_splash plugin from https://pub.dev/packages/flutter_native_splash and I use version 2.0.1+1
the problem that I'm having is that I decide what's the first screen that the user will see based on the initialization phase. I check the stored user token, see his premissions, verify them with the server, and forward him to him relevant route.
since the runApp() function executes in the background while the initialization phase is running I cannot choose the page that will be shown. and if I try to nativgate to a route in the initialization function I get an exception.
as a workaround for now I created an init_home route with FutureBuilder that awaits for a global variable called GeneralService.defaultRoute to be set and then changes the route.
class _InitHomeState extends State<InitHome> {
#override
Widget build(BuildContext context) {
return FutureBuilder<dynamic>(
future: () async {
var waitCount=0;
while (GeneralService.defaultRoute == "") {
waitCount++;
await Future.delayed(const Duration(milliseconds: 100));
if (waitCount>20) {
break;
}
}
if (GeneralService.defaultRoute == "") {
return Future.error("initialization failed");
}
Navigator.of(context).pushReplacementNamed(GeneralService.defaultRoute);
...
any ideas how to resolve this issue properly ?
I use a Stateful widget as Splash Screen.
In the build method, you just return the 'loading' view such as Container with a background color etc. (with texts or whatever you like but just consider it as the loading screen).
In the initState(), you call a function that we can name redirect(). This should be an async function that performs the queries/checks and at the end, calls the Navigator.of(context).pushReplacementNamed etc.
class _SplashState extends State<Splash> {
#override
void initState() {
super.initState();
redirect();
}
#override
Widget build(BuildContext context) {
return Container(color: Colors.blue);
}
Future<void> redirect() async {
var name = 'LOGIN';
... // make db calls, checks etc
Navigator.of(context).pushReplacementNamed(name);
}
}
Your build function just creates the loading UI, and the redirect function in the initState is the one running in the background and when it has finished computing, calls the Navigator.push to your desired page.

How to bypass a screen using navigator in flutter?

I am coming from android.
In android ,if i want to bypass an activity i will call intent method in onCreate something like this.
onCreate(){
if(condition satisfied){
Intent myIntent = new Intent(CurrentActivity.this, NextActivity.class);
CurrentActivity.this.startActivity(myIntent);
}
}
But i cant understand how to write this code in flutter and navigate another screen .
And I also don't know that this code will write in initState() or build().
note: In this case in If condition, I used StreamBuilder,
I need in following steps,
At screen start
Blank screen
if Condition will be true than return Container();
else leave this Screen(Activity) go to another screen.
Use this :
return isSomethingtrue ? HomeScreen() : OtherScreen(),
Then - In your HomeScreen() Build a Container()
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container();
}
}

Sometimes widgets not rendering in production builds

Sometimes the widget doesn't render properly. This only happens on the release build irrespective of the platform OS. Not only texts, sometimes other widgets like container, images won't get rendered either. This "blank state" occurs irrespective of the app's life cycle, meaning this happens both when the app is in the background and comes foreground or opened freshly. I can work around in the app normally without any issues but can't see any widgets which I'm supposed to see. We found there are multiple rebuilds happening in the stateful widget which is responsible for the checking and routing the users according to their login status.
Anything wrong in this method?
//initializations (state variable)
Widget widgetToRender = Splash();
var _firestore;
bool isLoading = false;
StreamSubscription userProfileSubscription;
//initalize function
void _intialise(BuildContext context) async {
if (isLoading) {
return;
}
_firestore = Provider.of<FirestoreService>(context);
userProfileSubscription = _firestore.userProfile.listen((userProfile) {
this.setState(() {
isLoading = true;
widgetToRender = App();
});
});
}
//build method
#override
Widget build(BuildContext context) {
if (widget.userSnapshot.connectionState == ConnectionState.active) {
if (widget.userSnapshot.hasData) {
_intialise(context);
} else {
setState((){
widgetToRender = AuthLanding();
})
}
}
return widgetToRender;
}
Flutter is a declarative framework.
You're going against it when you declare widgets like this:
Widget widgetToRender = Splash();
Also, you shouldn't call methods that execute imperative code from your build method:
_intialise(context);
My suggestion is to completely remove this code:
Widget widgetToRender = Splash();
var _firestore;
And rewrite things in a declarative manner. One way to do that is to use a StreamBuilder, and use its builder to return the widgets you want.
Addressing this may solve your rendering problems.
EDIT: step 1 would probably be to remove all imperative code and state variables in the build method:
#override
Widget build(BuildContext context) {
if (widget.userSnapshot.connectionState == ConnectionState.active) {
if (widget.userSnapshot.hasData) {
return App();
} else {
return AuthLanding();
}
}
return Scaffold(body: CircularProgressIndicator());
}
The load lag is because you are only working with widget.userSnapshot.connectionState == ConnectionState.active you will need to add progress indicators as the response from firestore depends on the network status of the user. Please let me know if that helps.

In Flutter: Is it possible to use a Future<bool> be used as a property in another widget?

I have a Card() widget which contains a ListTile() widget.
One of the ListTile() widget's properties is enabled. I would like to dynamically set the value of this enabled property by using the outcome of a Future<bool> which uses async and await. Is this possible?
Here is the Card() widget with the ListTile() in it
Card myCard = Card(
child: ListTile(
title: Text('This is my list tile in a card'),
enabled: needsToBeEnabled(1),
),
);
Here is my Future
Future<bool> cardNeedsToBeEnabled(int index) async {
bool thisWidgetIsRequired = await getAsynchronousData(index);
if (thisWidgetIsRequired == true) {
return true;
} else {
return false;
}
}
Other attempts
I have tried to use a Future Builder. This works well when I'm building a widget, but in this case I'm trying to set the widget's property; not build a widget itself.
You cannot do that for two reason:
enable does not accept Future<bool> but bool
you need to update the state after result is received (you need a StatefullWidget)
There are 1 million way to do what you want to do and one of this is FutureBuilder but if you want to not rebuild all widget you can use this flow (your main widget need to be Statefull):
create a local variable that contains your bool value, something like bool _enabled
on initState() method override you can launch the call that get asynchronous data and using the then() extension method you can provide the new state to your widget when the call will be completed.
Something like:
#override
void initState() {
super.initState();
getAsynchronousData(index).then((result) {
if (result == true) {
_enabled = true;
} else {
_enabled = false;
}
setState(() {});
});
}
assign the boolean var to the ListTile widget