Flutter - availableCameras() returns empty list - flutter

This code was working just fine a while ago, but is now misbehaving for no reasons, I tried to re-install the app 2 times, but didn't worked, what might be causing it? It was giving me a list of available cameras before, but after a hot-restart, the code is constantly breaking.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
cameras = await availableCameras(); // returns an empty list, which it shouldn't because I'm using a real device which has two physical cameras, all dependencies are added, all permissions are allowed.
runApp(
MyApp(),
);
}
Note: The error is coming when I try to access the cameras list, but it was able to access it two hours ago, why is it returning an empty list right now?

For all of you that may have done the same error as me by copy-pasting "camera" code from pubdev,
keep in mind that the code of "camera" was made for being main page, wich means _cameras is initialised by :
List<CameraDescription> _cameras = <CameraDescription>[];
but it's the main() function role to fill it, so if you call CameraApp() from outside of the page, main will not be triggered, therefore you need to modify the class like this:
class CameraApp extends StatelessWidget {
/// Default Constructor
const CameraApp({Key? key, required this.cameras}) : super(key: key);
final List<CameraDescription> cameras;
#override
Widget build(BuildContext context) {
_cameras=cameras;
return const MaterialApp(
home: CameraExampleHome(),
);
}
}
and call it from another page with:
await availableCameras().then((value) => Navigator.push(context,
MaterialPageRoute(builder: (_) => CameraApp(cameras: value))));
Like so the cameras are properly filled. That's my working solution.
Possible amelioration: Maybe the cameras list could be filled directly in CameraApp?

This error is most probably due to the "camera" plugin's internal working or due to Android OS's security reasons or something like that. The camera package is new, so you can expect such behaviors, but there are bunch of other enhanced packages as well based on the original one.
In my case, I used "flutter_camera" and modified the source code as per my needs in order to achieve the desired UI, and it works pretty good.
Update: I found out that the error was indirectly connected to "compileSdkVersion" in my app/build.gradle being set to 33 which was required by a random flutter plugin, setting it to 29 allowed me to access my camera and successfully executed availableCameras() method too but then the plugin can't be used.

Related

Clunky animation with Dismissible and Provider (NotifyListener)

I am trying to fix an issue that occurred when using the Dismissible widget and the Provider package.
When I dismissed a card this is called :
Provider.of<NMyProvider>(context, listen: false).toggleIsDone(object.id);
The provider :
Future<void> deleteNotebook(int id) async {
...
final notebookId = _items.indexWhere((notebook) => notebook.id == id);
...
_items.removeAt(notebookId);
notifyListeners();
}
This makes the animation clunky with noticeable lag but does not occur when removing the NotifyListeners.
Most probably the entire screen is being rebuilt upon notification from the provider. There are some solutions for that:
Move the Consumer<NMyProvider>/context.watch<NMyProvider>(context)/Provider.of<NMyProvider>(context, listen: true) closer to its usage. Flutter only rebuilds the widgets that use the same context;
Use a unique key for every notebook. Not the index position, but a unique key like the notebookId. Flutter will re-use the already built notebook widget where the key is the same in between builds;
Split the NMyProvider: Move everything that doesn't make sense to this provider into other providers. If a lot of unrelated states are together the chance of this provider being used by the root widget gets higher. NMyProvider should deal only with the list of notebooks state;

Navigating to another screen gets slower and slower each time i repeat click, go back, click, go back

I have a Navigator.push and MaterialPageRoute() to navigate to another screen. But navigation to other screens gets slower and slower becuase in my initState() i have a method which initializes the json data which i show each time i navigate to another screen. The json data is big and i only use one file with big json data which has objects and each object is shown i different screens. In my usecase i have to use one file with big json data.
In my initState() i initialize the data i grabbed with the method setTestData() and inside this method i set the data inside an object:
late Map<String, dynamic> grabbedData = {};
setTestData() async {
await TestData()
.getTestData()
.then((result) => setState(() => grabbedData = result));
}
#override
initState() {
setTestData();
super.initState();
}
In my view i can for example navigate to another screen and then show different objects inside the same object json i grabbed in setTestData(). I only use one view called AppView() to show different screen so when i navigate for example from screen A to B, both A and B screen are shown with AppView() widget. This is necessary for my use case which is irrelevant for this question.
This is the navigation which i use to load another screen and which technacly runs initState() from AppView() again because the previous route is also AppView():
Navigator.push(
context,
MaterialPageRoute(
maintainState: false,
builder: (_) => AppView(
selectedMenuItems:
grabbedData['data']
['views'][
widget.selectedMenuItems[
index]
[
'content']],
)));
But the problem is that each time i navigate to AppView() and click back on my phone to show previous data from AppView() and navigate again, it re-initializes the state and so the proces is slowed after i repeat it a couple of times. How do i solve this problem?
Better to use Provider package for this task. Provider does not rebuild the widget rather it only updates the state of the widget so it is faster. Then, you can get your data or data stream only once at the beginning of your app or a particular screen. No need to generate data each time. Also, provider automatically disposes data when the screen is closed. It is recommended by flutter team as well and an awesome package. For more example, check some YouTube videos. For your particular problem, I think it is better to use provider.value. Check the references and hopefully later on you will understand what is a provider.value object.
If you are generating new data every time then you need to set your provider.value each time where you are using Navigator.push, otherwise if you do not use your provider at the beginning of your app at MaterialApp section then after the push the provider won't be available.
As an example, to add provider inside a push please follow the following code snippet:
Navigator.push(context,
MaterialPageRoute(builder: ((context) {
return StreamProvider<User>.value(
initialData: initialUserData,
value: userDataStream,
child: const UpdateUser(),
);
})));
Next, access the value in the UpdateUser page like this:
final user = Provider.of<User>(context);
If you are not using any data stream then just try the normal ChangeNotifierProvider and get the value from the Consumer. Try some youtube tutorials and you will love it.

Flutter Hot Restart - suspect it populates class instances incorrectly

My app is in 2 parts. It creates a widget tree and populates a Stateful Widget Config.
I then pass this to phase 2 of my app as a static. (I tried various ways).
On a clean emulator it runs fine. When I run it again using Hot Restart my buildSitePath is passed a half-baked instance that did not go thru the proper constructors.
Thus where appCfg is a static Config appCfg
that has been confirmed to be populated properly the log statement in the following will execute after
the populating and yet it will be the half-baked instance that breaks the app. This happens on Hot Restart which supposedly restarts the app from the beginning.
routes:<String,WidgetBuilder>{
"/site":(BuildContext context) {assert(log("site: $appCfg"));
return buildSitePath(context,appCfg,title);}
},
The solution was to not use a static and to use a onGenerateRoute and pass the actual instance as a parameter. Thus, this worked.
onGenerateRoute: (settings) {
log("onGenerateRoute ${settings.name}");
if (settings.name == "/site") {
Config appCfg = settings.arguments;
log("onGenerateRoute $appCfg");
if (isMaterial(contextA)) {
return MaterialPageRoute(builder: (context) => buildSitePath(contextA, appCfg, title));
} else {
return CupertinoPageRoute(builder: (context) => buildSitePath(contextA, appCfg, title));
}
}
return null;
I looked and found no understanding as to what Hot Restart is doing with classes that might explain this.
What am I missing? Thanks.
As I said in my question my suspicion is that Flutter Hot Restart sees the static class instance that is not populated and jams the class in there using a default constructor.
Even if the class was not static but a member of another class the same ailment persisted.
I record this behavior here so that others need not waste as much time as I did to understand the problem.

How can you get the flutter visible page's state or context?

Sometimes an event (eg upload) starts async while the user is on one page. If they navigate away from that page the task's .then(...) will try to display the result on the page which is no longer visible, eg by means of a toast.
How can I get the context of the currently visible page at the time when the Future completes to display a snackbar, toast or dialog?
EDIT:
I see in the description of oktoast (https://pub.dev/packages/oktoast) that version 2 "Does not need the buildContext to be passed", and further
Quote:
Explain #
There are two reasons why you need to wrap MaterialApp
Because this ensures that toast can be displayed in front of all other
controls Context can be cached so that it can be invoked anywhere
without passing in context
This implies to me that there is a better way by providing a Material app ancestor somewhere....
For a simple Solution I would use the Provider Package. Keyword here is StateManagement (https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple)
The Model would look sth like this:
class UploadModel extends ChangeNotifier {
final bool loading = false;
void uploadXY() async{
loading = true;
// This call tells the widgets that are listening to this model to rebuild.
notifyListeners();
await realUploadStuff();
loading = true;
notifyListeners();
}
}
To start the upload:
Provider.of<UploadModel>(this).uploadXY()
To react if loading-bool changes:
if(Provider.of<UploadModel>(this).loading)...
You can find a simple Example here:
https://github.com/flutter/samples/blob/master/provider_counter/lib/main.dart

Flutter using GlobalKey to trigger another states function

Basic App to reproduce the error has two widget
Home Widget contains a gesture detector triggering the following function:
GlobalKey<MainMapState> mapKey = GlobalKey<MainMapState>();
void getCurrentLocation() async{ // I am using async property for something different but it not our concern right now
mapKey.currentState.asdd();
}
MainMap is a stateful widget and it's state contains the following function:
void asdd(){
print("triggered");
}
As a result I am getting this
Note: I am using GlobalKey to animate CameraPosition to my current location on GoogleMap widget which is inside the MainMapState
I'm sorry I don't have enough reputation points to merely comment on your post.
Based on the error you are receiving I suspect you have the same issue to which I have yet to find a correct answer. A couple of alternate approaches are listed on my same question as noted at link:
How to smoothly update a Flutter AnimatedList without using a GlobalKey.currentState?