Im going to explain my use case for this
I have a inherited widget that has some values like BaseApiUrl and appname
class Config extends InheritedWidget {
Config({
#required this.appName,
#required this.flavorName,
#required this.apiBaseUrl,
#required Widget child,
}) : super(child: child);
final String appName;
final String flavorName;
final String apiBaseUrl;
static Config of(BuildContext context) {
return context.inheritFromWidgetOfExactType(Config);
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
This works fine inside of context based classes, but what if i have this class where i need apiBaseUrl
import 'dart:async';
import 'package:http/http.dart';
class LoginRequest {
Client client = Client();
Future<Response> login(credentials) async => await client.get('');
}
Can be this accomplished ?
The context is important so Flutter knows where in the tree to look for the instance of the InheritedWidget, since there could be multiple. If this isn't true for your use case, you could consider moving the required data into a plain singleton class (not a widget).
Related
So I wanted to use Hive for storing notes in a Calendar app but I am stuggling so much with implementing Hive and a ChangeNotifierProvider together . If anyone has an idea on what to do I would like to see it.
Here is my code until now :
#HiveType(typeId: 0)
class EventsBox extends HiveObject {
EventsBox({required this.date, required this.eventsList});
#HiveField(0)
DateTime date;
#HiveField(1)
List<CleanCalendarEvent> eventsList;
}
And here is the FutureProvider that is needed :
final hiveProvider = FutureProvider<HiveDB>((_) => HiveDB.create());
class HiveDB {
var _events;
HiveDB._create() {}
static Future<HiveDB> create() async {
final component = HiveDB._create();
await component._init();
return component;
}
_init() async {
Hive.registerAdapter(EventsBoxAdapter());
this._events = await Hive.openBox<EventsBox>('events');
}
storeEvent(EventsBox eventsMap) {
this._events.put('events', eventsMap);
}
EventsBox getEvents() {
return this._events.get('events');
}
}
I want to use ChangeNotifierProvider and not FutureProvider
The goal of the provider framework is to inject dependencies into your app without recreating them all the time. In your example, you have your HiveDB class which is what you want to inject into your app for various other widgets to use it.
IMO, the general approach of "providing" dependencies is:
Create dependency instances outside the app (Typically in the main() function)
Create a provider which injects this dependency object
Wrap your MaterialApp with the provider
Supply your dependency object into the provider wrapper
Use the Provider<DependencyClass>.of(context) to access your dependency wherever you need it in your app.
Let's see how this applies to your code:
1. Setup your Hive models and HiveDB
lib/models/event_box.dart
#HiveType(typeId: 0)
class EventsBox extends HiveObject {
#HiveField(0)
DateTime date;
#HiveField(1)
List<CleanCalendarEvent> eventsList;
EventsBox({
required this.date,
required this.eventsList,
});
}
lib/models/hive_db.dart
class HiveDB {
var _events;
HiveDB._create() {}
static Future<HiveDB> create() async {
final component = HiveDB._create();
await component._init();
return component;
}
_init() async {
Hive.registerAdapter(EventsBoxAdapter());
this._events = await Hive.openBox<EventsBox>('events');
}
storeEvent(EventsBox eventsMap) {
this._events.put('events', eventsMap);
}
EventsBox getEvents() {
return this._events.get('events');
}
}
In this case, you want to "provide" (or inject) an instance of HiveDB to your app.
2. Create a provider for your HiveDB instance
lib/models/hive_db_provider.dart
class HiveDbProvider extends StatelessWidget {
final HiveDB db;
final Widget child;
const HiveDbProvider({
Key? key,
required this.db,
required this.child,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<HiveDB>.value(
value: db,
child: Consumer<AppCache>(
builder: (context, db, _) {
return child;
},
),
);
}
}
3. Create HiveDB instance in main()
lib/main.dart
void main() async {
final HiveDB db = await HiveDB.create();
runApp(
MyApp(db: db)
);
}
...
4. Inject the HiveDB instance using the provider
lib/main.dart
...
class MyApp extends StatelessWidget {
final HiveDB db;
const PayBuddy({
Key? key,
required this.db,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return HiveDbProvider(
db: db,
child: CacheProvider(
cache: cache,
child: MaterialApp(
...
),
),
);
}
}
5. Access "provided" instance using Provider anywhere in your app
lib/pages/some_screen.dart
import "package:provider/provider.dart";
...
class SomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Access HiveDB instance
final hiveDb = Provider<HiveDB>.of(context);
...
}
}
Conclusion
The sample uses the Provider package which simplifies using provider usage in Flutter.
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.
Am trying to connect on a SignalR server with flutter, and I tried to use Provider to access the data which comes to another Widget, but am receiving a null value. This is the SignalR library for flutter am using.
Below is my Class which extends a ChangeNotifier :
class BreakingNewsSignalRClient with ChangeNotifier {
String _breakingNews;
Future<void> getBreakingNews() async {
final hubConnection = HubConnectionBuilder()
.withUrl('https://services.xxxx.com/breakingNews')
.build();
await hubConnection.start();
hubConnection.on("UpdateBreakingNews", (data){
_breakingNews = data.toString();
print(data.toString());
notifyListeners();
});
hubConnection.onclose((error) => print("Connection Closed"));
}
String get breakingNewsFunction => _breakingNews;
}
class BreakingNewsModel {
final String News;
BreakingNewsModel(this.News);
}
As you can see the code above, am having a getter String get breakingNewsFunction => _breakingNews;, this is called in another Stateful Widget to populate some data from the SignalR server, but it returns null in the other widget, however, when you try to print data here in the getBreakingNews method the data is shown.
Below is another Widget class which receives the data :
class BreakingNews extends StatefulWidget {
const BreakingNews({Key key, #required this.article, #required this.index})
: super(key: key);
final Article article;
final int index;
#override
_BreakingNewsState createState() => _BreakingNewsState();
}
class _BreakingNewsState extends State<BreakingNews> {
settings() async{
var breakingNewsInfo =
Provider.of<BreakingNewsSignalRClient>(context, listen: false);
await breakingNewsInfo.getBreakingNews();
print('lets see -- ${breakingNewsInfo.breakingNewsFunction}');
}
#override
void initState() {
// TODO: implement initState
settings();
super.initState();
}
}
So when you look at this line print('lets see -- ${breakingNewsInfo.breakingNewsFunction}');, it prints null, am still wondering what am doing wrong here.
Kindly need some help.
Did you try data[0]?
Can you write it?
_breakingNews = data[0].toString();
instead of
_breakingNews = data.toString();
This is pretty simple. Your Future<void> getBreakingNews() async returns void and therefore null. Just adjust the return type to whatever you need.
Edit:
Actually, the problem is you are not calling your getter here.
await breakingNewsInfo.getBreakingNews();
So either return your result from the function, or call the getter. Either should work.
I am facing a few strange issues with Flutter. I do have very little knowledge about Flutter. I am learning it.
class ViewOtherProfile extends StatefulWidget {
final String userName;
final int points;
const ViewOtherProfile({
#required this.userName,
#required this.points,
});
You can see i am getting userName and Points data as argument.
I want to print this argument in the page. Like this
class _ViewOtherProfileState extends State<ViewOtherProfile> {
..........
void initState(){
print(points);
deviceInfo();
super.initState();
print(userName);
]);
}
............
Now problem is i am getting error.
Undefined name 'userName'.
Try correcting the name to one that is defined, or defining the name.
Any reason why i am getting this error and how i can resolve it.
Thanks to #jamesdlin
I tried to put it like this
print(ViewOtherProfile.userName);
but now i am getting another error.
Instance member 'userName' can't be accessed using static access.
There are two main types of widgets in Flutter. StatelessWidget and StatefullWidget. A StatelessWidget is built only one when the UI builds and is never rebuilt. Meanwhile, a StatefulWidget can be rebuilt every time if a call for setState is made.
Hence, for a StatefulWiget, there is a need to track the state of the class by extending the main class with a State class.
You have to note that the scope of variables in those two types of widgets can vary. For example...
class ExampleStateless extends StatelessWidget {
final String userName;
final int points;
const ExampleStateless({
#required this.userName,
#required this.points,
});
#override
Widget build(BuildContext context){
print(userName); print(points);
return Something();
}
}
Note that for the stateful widget, there are two classes and each class has its scope. The superclass ExampleStateful can share its scope to its subclass _ExampleStatefulState via the widget
class ExampleStateful extends StatefulWidget {
final String userName;
final int points;
Static final string id = "exampleState";
const ExampleStatefull({
#required this.userName,
#required this.points,
});
// scope 1
#override
_ExampleStatefulState createState() => _ExampleStatefulState();
}
class _ExampleStatefulState extends State<ExampleStateful>{
// scope 2
final String userName2 = null;
#override
void initState(){
super.initState();
print(widget.userName);
print(userName2);
print(ExampleStateful.id); // you can do this only if variable is static.
}
}
What is in scope 1 can be accessed in scope 2 via the widget properties. eg print(widget.userName); instead of print(userName);
Is passing a GlobalKey down the tree using an InheritedWidget an antipattern? The stateful widget using that key is re-created (i.e. a new state this initState/disposed) every time its subtree is re-built.
My InheritedWidget looks like:
import 'package:flutter/material.dart';
import '../widgets/carousel.dart';
import '../widgets/panel/panel.dart';
class _CarouselKey extends GlobalObjectKey<CarouselState> {
const _CarouselKey(Object value) : super(value);
}
class _ProgressiveChatHeaderKey extends GlobalObjectKey<PanelScaffoldState> {
const _ProgressiveChatHeaderKey(Object value) : super(value);
}
class DimensionScopedKeyProvider extends InheritedWidget {
final _CarouselKey parallelBubbleCarouselKey;
final _ProgressiveChatHeaderKey progressiveChatHeaderKey;
final String keyString;
DimensionScopedKeyProvider({
Key key,
#required this.keyString,
#required Widget child,
}) : parallelBubbleCarouselKey = _CarouselKey(keyString),
progressiveChatHeaderKey = _ProgressiveChatHeaderKey(keyString),
super(key: key, child: child);
static DimensionScopedKeyProvider of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(DimensionScopedKeyProvider)
as DimensionScopedKeyProvider);
}
#override
bool updateShouldNotify(DimensionScopedKeyProvider oldWidget) => oldWidget.keyString != keyString;
}
And this InheritedWidget is rendered with a constant keyString, meaning that 1) updateShouldNotify always returns false and 2) the hashCode of the GlobalKeys passed to my build methods via DimensionScopedKeyProvider.of() are always identical.
The stateful widget builds something like
GlobalKey<PanelScaffoldState> get _headerKey => //
DimensionScopedKeyProvider.of(context).progressiveChatHeaderKey;
// ...
PanelScaffold(
key: _headerKey,
// ...
)
When I change a property that affects the subtree that the PanelScaffold lives in, though, a new PanelScaffoldState is created and the old one is disposed, even though the widget tree hasn't changed structure and the _headerKey hasn't changed either.
I also able to solve this problem, but I have no idea why it works.
The solution is to cache the access to the GlobalKey in didChangeDependencies
#override
void didChangeDependencies() {
super.didChangeDependencies();
_headerKey ??= DimensionScopedKeyProvider.of(context).progressiveChatHeaderKey;
}
.... and now everything is working as expected again—the rebuilds re-parent the existing state.
Does anyone know why caching the getter to the GlobalKey is the key here?