Flutter access ModalRoute.of(context)!.settings.arguments before Widget Build - flutter

I have arguments that are being passed through a namedRoute. I access them currently with:
final args = ModalRoute.of(context)!.settings.arguments as UserPack;
In my widget Build. I am trying to create a function for a stream that needs data from args but unfortunately the I cant access args outside of the Widget Build Which means I can not get the packID for the streamIndividualPackList(). I need to build that Stream before the Widget Build.
class IndividualPack extends StatefulWidget {
const IndividualPack({Key? key}) : super(key: key);
#override
State<IndividualPack> createState() => _IndividualPackState();
}
Stream<QuerySnapshot<Map<String, dynamic>>> streamIndividualPackList(
String packID) {
FirebaseFirestore db = FirebaseFirestore.instance;
return db
.collection('PackList')
.doc(packID)
.collection('PackContents')
.orderBy('itemName')
.snapshots();
}
class _IndividualPackState extends State<IndividualPack> {
FirebaseAnalytics analytics = FirebaseAnalytics.instance;
//gets the Firebase db
FirebaseFirestore db = FirebaseFirestore.instance;
String userID = FirebaseAuth.instance.currentUser!.uid;
final PageController pageViewController = PageController();
#override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as UserPack;

ModalRoute depends on context, you can use nullable data with addPostFrameCallback to assign it.
int? data;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
data = ModalRoute.of(context)?.settings.arguments as int?;
});
}
You just nullable data inside build method.
int? data;
#override
Widget build(BuildContext context) {
data ??= ModalRoute.of(context)?.settings.arguments as int?;
Do a null check before using nullable data.

Related

DocumentSnapshot returning null and new screen stuck on loading

When this widget is called, its stuck on loading animation, and when hot reloaded, it shows the transactions
`
class TransactionList extends StatefulWidget {
final int groupIndex;
final String groupUid;
const TransactionList(
{super.key, required this.groupIndex, required this.groupUid});
#override
State<TransactionList> createState() => _TransactionListState();
}
class _TransactionListState extends State<TransactionList> {
#override
Widget build(BuildContext context) {
final groupTransaction = Provider.of<DocumentSnapshot?>(context);
if (groupTransaction == null) return const Loading();
return transactionItemsBuilder(groupTransaction);
}`
edit:
this is firebase instance created,
class DatabaseServices {
final String? uid;
DatabaseServices({this.uid});
final CollectionReference groupCollection =
FirebaseFirestore.instance.collection('groups');
Stream<DocumentSnapshot> get transactions {
return groupCollection.doc(uid).snapshots();
}
}
and this is my stream provider
StreamProvider<DocumentSnapshot?>.value(
initialData: null,
value: DatabaseServices(uid: widget.item[widget.index].id)
.transactions,
child: TransactionList(
groupIndex: widget.index,
groupUid: widget.item[widget.index].reference.id),
),
Solved this by providing initial data to the stream provider. The data was obtained earlier.

I want to change the height of the appbar using the value of json style file

I have a app_bar_base.dart file where i have an AppBar.
class AppBarBase extends StatelessWidget implements PreferredSizeWidget {
late double appBarHeight = LoadAppStyle().loadAppStyle();
AppBarBase({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return AppBar();
}
#override
Size get preferredSize => Size.fromHeight(appBarHeight);
}
I am calling the method LoadAppStyle().loadAppStyle() from the file load_app_style:
class LoadAppStyle {
loadAppStyle() async {
String jsonData =
await rootBundle.loadString('assets/config/app_style.json');
Map<String, dynamic> data = jsonDecode(jsonData);
var getHeight = double.parse(data["app_bar"]["app_bar_height"]);
return getHeight;
}
}
In the load_app_style.dart file i grab the value of app_bar_heigt from the app_style.json
in app_style.json i have key app_bar_height where i want to change the value manually to change the height of the App bar
{
"app_bar":
{
"app_bar_height": 78
},
}
But for some reason i get the error : type 'Future<dynamic>' is not a subtype of type 'double'
You can add the type to your loadAppStyle method. Since your method is async it returns a Future.
Future<double> loadAppStyle() async {
...
return getHeight;
}
Now your error should be
type 'Future<double>' is not a subtype of type 'double'
Since your method returns a Future you have to use await to get the value.
loadAppStyle() // Future<double>
await loadAppStyle() // double
If you want to use a value of a Future inside a Widget, have a look at FutureBuilder.
For your case you could e.g. use the FutureBuilder to retrieve the height and then pass it to AppBarBase
FutureBuilder<double>(
future: loadAppStyle(),
builder: (context, snapshot) {
if(snapshot.hasData) {
return AppBarBase(height: snapshot.data);
} else {
return const Center(child: CirclularProgressIndicator));
}
}
)
And change your AppBarBase to the following.
class AppBarBase extends StatelessWidget implements PreferredSizeWidget {
AppBarBase({
Key? key,
required this.height,
}) : super(key: key);
final double height;
#override
Widget build(BuildContext context) {
return AppBar();
}
#override
Size get preferredSize => Size.fromHeight(height);
}
In your example, loadAppStyle() has no defined return type (dynamic) and it is marked as async (Future), hence the return type of this function is Future<dynamic>. Size.fromHeight function requires the double value, hence you get this error - the expected type is double, but Future<dynamic> was found here.
To resolve the type differences, you should set the return type of a function:
class LoadAppStyle {
Future<double> loadAppStyle() async {
String jsonData =
await rootBundle.loadString('assets/config/app_style.json');
Map<String, dynamic> data = jsonDecode(jsonData);
var getHeight = double.parse(data["app_bar"]["app_bar_height"]);
return getHeight;
}
}
Now, since your function is async, you must wait for your Future to finish and only then you could retrieve the double value. It would look something like this:
class AppBarBase extends StatelessWidget implements PreferredSizeWidget {
late double appBarHeight = await LoadAppStyle().loadAppStyle(); // Throws error
AppBarBase({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return AppBar();
}
#override
Size get preferredSize => Size.fromHeight(appBarHeight);
}
However, this throws an error since you cannot use the asynchronous code when initialising a value this way. What could be a better way to do this is to wait for this value somewhere outside of your widget and pass the result via the constructor:
class AppBarBase extends StatelessWidget implements PreferredSizeWidget {
final double appBarHeight;
AppBarBase({
required this.appBarHeight,
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return AppBar();
}
#override
Size get preferredSize => Size.fromHeight(appBarHeight);
}
This way, you separate your UI code from the widget. Anyway, the way of keeping this UI-specific configuration inside the JSON file sounds way overengineered - consider just passing this value via constructor directly, like: AppBarBase(appBarHeight: 78).

LateInitializationError: Field 'authProvider' has not been initialized

I got the error LateInitializationError: Field 'authProvider' has not been initialized. for the following:
class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key);
#override
State createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
final FirebaseMessaging firebaseMessaging = FirebaseMessaging.instance;
final ScrollController listScrollController = ScrollController();
late AuthProvider authProvider;
String? currentUserId;
late MainProvider mainProvider;
Debouncer searchDebouncer = Debouncer();
StreamController<bool> btnClearController = StreamController<bool>();
TextEditingController searchBarTec = TextEditingController();
#override
void initState() {
super.initState();
mainProvider = context.read<MainProvider>();
if (authProvider.getUserFirebaseId()!.isNotEmpty == true) {
currentUserId = authProvider.getUserFirebaseId()!;
} else {
return null;
}
registerNotification();
listScrollController.addListener(scrollListener);
}
//more code below
This code is from a Demo: Chat App with Flutter
How do I initialize the fields for authProvider, mainProvider etc?
Late initialization error means that a variable marked as late (in your case authProvider) was not initialized before it was accessed.
on a widget the first thing you execute is the constructor and then you execute the initstate. your constructor has nothing and initstate reads authProvider.getUserFirebaseId().
If you take a look at the video's github page, you will see that before calling authProvider, they initialize it by running the following line:
authProvider = context.read<AuthProvider>();
homeProvider = context.read<HomeProvider>();
If you are following a tutorial, the tutorial is either outdated or not complete if it has this sort of error.

flutter: how to get data from db and using it through the whole app

I am so confused about state management.
Below is I pass down data through widgets.
List<AppUser> userList = List<AppUser>();
List<List<MessageType>> messageLists = new List<List<MessageType>>();
#override
void initState() {
super.initState();
loadUsers();
}
Future<void> loadUsers() async {
userList.clear();
userList.addAll(await AppUser.getRelatedUsers(customer.customerID));
defaultUser = await AppUser.getDefaultUser(customer.customerID);
if (defaultUser != null && !await defaultUser.hideUserTab()) {
userList.add(defaultUser);
}
await loadMessageList();
}
Then I pass the userList and messageList to another stateful widget. But what if I want to have those data through the whole app using inherited widget or provider or bloc.
MessageTypePage(
messageTypeList: messageLists[tabIndex],
currentUser: userList[tabIndex],
);
How can I possible to get the data from db and store them in inherited widget then using those data? I am so confused.
class StateContainer extends StatefulWidget {
final Widget child;
final List<AppUser> userList;
final List<Message> messageList;
StateContainer({#required this.child, this.userList, this.messageList});
static StateContainerState of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<_InheritedStateContainer>().data;
}
#override
StateContainerState createState() => new StateContainerState();
}
class StateContainerState extends State<StateContainer> {
List<AppUser> userList = List<AppUser>();
List<List<MessageType>> messageLists = new List<List<MessageType>>();
#override
Widget build(BuildContext context) {
return _InheritedStateContainer(
data: this,
child: widget.child,
);
}
}
class _InheritedStateContainer extends InheritedWidget {
final StateContainerState data;
_InheritedStateContainer({Key key, #required this.data, #required Widget child}) : super(key: key, child: child);
#override
bool updateShouldNotify(_InheritedStateContainer oldWidget) {
return true;
}
}
In my opinion, the best approach is to use Provider or Bloc. There is a flutter codelab that uses Provider to do something very similar to what you are doing. It stores a list of items (in your case that would be Users) that can be used throughout the app. It also shows you how to manipulate the list in various ways.
The codelab is here. I think it would help you out.

How to get id from StatefulWidget in State?

I want to load comments in my post here. For that I need to send post id to my HTTP get request. post id I sent from another page. but I want to assign that String id; value to final response = await http.get("http://$ip:$apiPort/solutions/$id"); here id in Flutter.
How can I do that?
to clear what I want
my code is
class Solutions extends StatefulWidget {
String id ;
final bool isEditMode;
Solutions({
this.id,
this.isEditMode,
});
#override
_SolutionsState createState() => _SolutionsState();
}
class _SolutionsState extends State<Solutions> {
List data;
var ip = Configuration.yourIp;
var apiPort = Configuration.yourApiPort;
Future<List> getData() async {
final response = await http.get("http://$ip:$apiPort/solutions/$id");
return json.decode(response.body);
}
#override
void initState() {
super.initState();
this.getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
Future<List> getData() async {
final response = await http.get("http://$ip:$apiPort/solutions/${widget.id}");
return json.decode(response.body);
}
This should to the trick.
From the State class (_SolutionState in your case), you can access the widget (Solution in your case) members by finding them in widget.
BONUS
Your id should be final, since StatefulWidget is marked as an immutable class, which means its members should all be final. You have surely a warning about this from your IDE.