I just want initialize provider only one time with widget param. For some reason I can't use .family. I'm not sure if this is the right way. Can you check this? Thank you.
StateProvider<String> valueStateProvider;
class Widget extends HookWidget {
final String value;
Widget({#required this.value}) {
valueStateProvider = StateProvider<String>((ref) => this.value);
}
}
Finally I found the right way to do this. There is special provider type for this situation. ScopedProvider.
final scopedProvider = ScopedProvider<String>((_) => throw UnimplementedError());
class Widget extends HookWidget {
Widget({#required this.value});
final String value;
#override
Widget build(BuildContext context) {
return ProviderScope(
overrides: [scopedProvider.overrideWithValue(this.value)],
child: AnotherWidget()
);
}
}
So you can use scopedProvider in AnotherWidget. Yay!
Related
I hope you could help me!
Error saying 'tables' has not been initiliazed. But when I set tables = [] instead of
widget.data.then((result) {tables = result.tables;})
it works. I think the problem comes from my app state data which is a Future.
My simplified code:
class NavBar extends StatefulWidget {
final Future<Metadata> data;
const NavBar({Key? key, required this.data}) : super(key: key);
#override
State<NavBar> createState() => _NavBarState();
}
class _NavBarState extends State<NavBar> {
late List<MyTable> tables;
#override
void initState() {
widget.data.then((result) {
tables = result.tables;
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: buildPages(page.p)
)
);
}
Widget buildPages(index){
switch (index) {
case 0:
return ShowTablesNew(tables: tables);
case 1:
return const Details();
case 2:
return const ShowTables();
default:
return const ShowTables();
}
}
}
Future doesn't contain any data. It's an asynchronous computation that will provide data "later". The initialization error happens because the variable 'tables' is marked as late init but is accessed before the future is completed, when in fact it's not initialized yet.
Check this codelab for async programming with dart.
For your code you can use async/await in the initState method doing something like this
String user = '';
#override
void initState() {
asyncInitState();
super.initState();
}
void asyncInitState() async {
final result = await fetchUser();
setState(() {
user = result;
});
}
but since you're using a list of custom objects the most straightforward way is probably to use a FutureBuilder widget
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).
I have a simple client code in which I'm trying to pass the WebSocketChannel instance to an inner stateful widget, and for some reason when I try to run the code the app crushes and displays on the screen "Unexpected null value. See also: https://flutter.dev/docs/testing/errors". It would be greatly appreciated if someone could explain to me why this happens and how to fix it.
The code:
import 'package:flutter/material.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
class TestWidget extends StatefulWidget {
final WebSocketChannel channel;
const TestWidget(this.channel);
#override
_TestWidgetState createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
String buttonText = '';
_TestWidgetState() {
widget.channel.stream.listen((data){
setState(() {buttonText = data;});
});
}
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: (){widget.channel.sink.add('hello');},
child: Text(buttonText)
);
}
}
class App extends StatelessWidget {
final WebSocketChannel channel = WebSocketChannel.connect(
Uri.parse('ws://localhost:8000/')
);
#override
Widget build(BuildContext context) {
return MaterialApp(home: Scaffold(body:
TestWidget(channel)
));
}
}
void main() {
runApp(App());
}
Thanks in advance for the help.
Any particular reason why you put
final WebSocketChannel channel = WebSocketChannel.connect(
Uri.parse('ws://localhost:8000/')
);
in App? Move this line code to TestStateWidget constructor. It's best practice u follow null safety method when try to access an object.
I am new to flutter. My flutter widget tree is getting deeper and deeper, I want to know which is the best method to pass data from a root to a widget which is much deeper from it. I'm currently passing it as a constructor from widget to widget.
My current implementation is given below
Level1(data: data)
Level2(data: data)
Level3(data: data)
Level4(data: data)
suppose my data is retrieved from DB in level1 widget and it is used in level4 widget. As we see, my current implementation is considerably messy. How this is generally done? what is the best practice?
You might like to use Provider. You can find more about it here.
Basically, you create provider of the data at the top-most level like:
class Level1 {
#override
Widget build(BuildContext context) {
Provider<Data>(
create: (_) => Something(),
child: Level2 (
// stuff of level 2
),
),
}
}
Something in this case bight be a change notifier.
You can then access it at a lower level with:
final provider = Provider.of<Something>(context);
Inherited widget - If you want to avoid using any third party library..
More can be found here - https://medium.com/#mehmetf_71205/inheriting-widgets-b7ac56dbbeb1
and here https://www.youtube.com/watch?v=Zbm3hjPjQMk
class MyInheritedWidget extends InheritedWidget {
final int accountId;
final int scopeId;
MyInheritedWidget(accountId, scopeId, child): super(child);
#override
bool updateShouldNotify(MyInheritedWidget old) =>
accountId != old.accountId || scopeId != old.scopeId;
}
class MyPage extends StatelessWidget {
final int accountId;
final int scopeId;
MyPage(this.accountId, this.scopeId);
Widget build(BuildContext context) {
return new MyInheritedWidget(
accountId,
scopeId,
const MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
const MyWidget();
Widget build(BuildContext context) {
// somewhere down the line
const MyOtherWidget();
...
}
}
class MyOtherWidget extends StatelessWidget {
const MyOtherWidget();
Widget build(BuildContext context) {
final myInheritedWidget = MyInheritedWidget.of(context);
print(myInheritedWidget.scopeId);
print(myInheritedWidget.accountId);
...
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.