How do I have my app change screens based on my user state? - flutter

I am using the ScopedModel pattern, but I am also interested how this same problem is addressed in the similar Provider pattern.
Currently I have a ScopedModel with a bool exposed called loggedIn. When the FirebaseonAuthStateChanged stream changes user log in state, my ScopedModel changes that bool, and calls NotifyListeners. All straight forward stuff.
Now I am confused as to the best way to push or pop routes based on this ScopedModel.
Should all my logged in screens (screens that require a user) have the following code in build method?
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!auth.hasUser)
Navigator.of(context).pushNamedAndRemoveUntil('/entry', (Route<dynamic> route) => });
});
That seems a little excessive to have this code on every single screen. Is there a way I can define this log screen change behaviour somewhere only once?

create a Widget for it ;)
class Validation extends StatefulWidget {
final Function validator;
final Widget child;
const Validation({Key key, this.validator, this.child}) : super(key: key);
#override
_ValidationState createState() => _ValidationState();
}
class _ValidationState extends State<Validation> {
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
widget.validator();
});
super.initState();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
now use it everywhere
#override
Widget build(BuildContext context) {
return Validation(
validator: (){
if (!auth.hasUser){
Navigator.of(context).pushNamedAndRemoveUntil('/entry', (Route<dynamic> route) => false);
}
},
child: MyAwesomePage(),
);
}
you can further simplify if the validation is same everywhere or create multiple validation widget according to the validations required,
FOR YOUR CASE
class LoginValidation extends StatefulWidget {
final String routeIfNotLoggedIn;
final Widget child;
const LoginValidation({Key key, this.routeIfNotLoggedIn, this.child}) : super(key: key);
#override
_LoginValidationState createState() => _LoginValidationState();
}
class _LoginValidationState extends State<LoginValidation> {
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (!auth.hasUser){
Navigator.of(context).pushNamedAndRemoveUntil(widget.routeIfNotLoggedIn, (Route<dynamic> route) => false);
}
});
super.initState();
}
}
and use it
#override
Widget build(BuildContext context) {
return LoginValidation(
routeIfNotLoggedIn: "/myLoginRoute",
child: MyAwesomePage(),
);
}

Related

Update TextEditingController Text with Riverpod

I'm new to Riverpod and am trying to migrate an app over from Provider. If I had a TextField and wanted to set its value based on my Provider model, I would do this:
class MyWidget extends StatefulWidget{
const MyWidget({ Key? key }) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var controller = TextEditingController();
#override
void didChangeDependencies() {
super.didChangeDependencies();
//Set the value here...
var model = Provider.of<Model>(context);
controller.text = model.name;
}
Widget build(BuildContext context) {
return TextField(controller: controller)
}
}
As I understand it, didChangeDependencies() would listen to changes from Provider.of<Model>(context) and update my controller accordingly.
I'm trying to pull off the same thing with Provider, but I can't ever get the TextField's value to show up.
class MyWidget extends ConsumerStatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
ConsumerState<ConsumerStatefulWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends ConsumerState<MyWidget> {
var controller = TextEditingController();
#override
void didChangeDependencies() {
super.didChangeDependencies();
//Trying the same thing here...
final name = ref.watch(providerName);
controller.text = name;
}
Widget build(BuildContext context) {
final name = ref.watch(providerName);
return Column(
children: [
//This doesn't work:
TextField(controller: controller),
//I know my provider has the value, because this works fine:
Text(name),
]
}
}
How can I get my TextEditingController's text property to update?
From Riverpod official website
///1.Create a [StateNotifier] sub-class, StateNotifier is something where you can define functions that can change your state like in this state is of String type, you also can use objects (Classes instead of primitive types)
class Counter extends StateNotifier<String> {
Counter() : super('');
void changeText(String text){
state=text;
}
///2.Create a provider [StateNotifierProvider] with this you can use in your widget
final counterProvider = StateNotifierProvider<Counter, String>((ref) {
return Counter();
});
///3.Consume the Provider this is how we can attach state with our widget
class Home extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final text = ref.watch(counterProvider);
return Text('$text');
}
}
so here you can add you widget like button and onTap executes the code like
onTap()=>changeText(textController.text);
So your text [Text('$text');] will automatically change.
String inputText = controller.text;

create: (_) => AboutBloc(widget.seed)..add(CallforAbout()) not Working in flutter

I want to fire Bloc Event when Screen loads but ..add is not working , also tried using initState() still State is intial.
Also used MultiblocProvider it works but in multiBlocProvider cant pass aurgumnets Required to call API.
class AboutThisProfile extends StatefulWidget {
String seed;
AboutThisProfile({
Key? key,
required this.seed,
}) : super(key: key);
#override
_AboutThisProfileState createState() => _AboutThisProfileState();
}
class _AboutThisProfileState extends State<AboutThisProfile> {
Widget build(BuildContext context) {
return BlocProvider<AboutBloc>(
create: (_) => AboutBloc(widget.seed)..add(CallforAbout()),
child: AboutScafold(
seed: widget.seed,
),
);
}
}

Screen's build method triggered when I use the navigator with rootNavigator

There is an interesting problem. I'm using Flutter 2.0 in my app. It has a bottom navigation bar. When I push to the new screen using "Navigator.of(context, rootNavigator: true)" along with the new screen, the screen that provides navigation is also rebuilding.
CategoriesView build() method
#override
Widget build(BuildContext context) {
print("Building: CategoryView");
return mobileBody;
}
ProductDetailView build() method
#override
Widget build(BuildContext context) {
print("Building: ProductDetailView");
return mobileBody;
}
When i use the "context.rootNavigator.pushNamed(ProductDetailView.route);" in CategoryView output is:
I/flutter (21910): Building: ProductDetailView
I/flutter (21910): Building: CategoryView
Without rootNavigator output is (context.navigator.pushNamed(ProductDetailView.route);):
I/flutter (21910): Building: ProductDetailView
Navigator extensions:
extension NavigationExtension on BuildContext {
NavigatorState get rootNavigator => Navigator.of(this, rootNavigator: true);
NavigatorState get navigator => Navigator.of(this);
}
Why is this happening? How can I prevent this?
I have met the same issue too, currently use a StatefulWidget wrapper for it.
class WrapperStateless extends StatefulWidget {
const WrapperStateless({Key key}) : super(key: key);
#override
_WrapperStatelessState createState() => _WrapperStatelessState();
}
class _WrapperStatelessState extends State<WrapperStateless> {
TabA tabA;
#override
void initState() {
super.initState();
tabA = TabA();
}
#override
Widget build(BuildContext context) {
// return tabA;
return tabA;
}
#override
void didUpdateWidget(WrapperStateless oldWidget) {
super.didUpdateWidget(oldWidget);
// setState(() {});
}
}

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.

Passing data to StatefulWidget and accessing it in it's state in Flutter

I have 2 screens in my Flutter app: a list of records and a screen for creating and editing records.
If I pass an object to the second screen that means I am going to edit this and if I pass null it means that I am creating a new item. The editing screen is a Stateful widget and I am not sure how to use this approach https://flutter.io/cookbook/navigation/passing-data/ for my case.
class RecordPage extends StatefulWidget {
final Record recordObject;
RecordPage({Key key, #required this.recordObject}) : super(key: key);
#override
_RecordPageState createState() => new _RecordPageState();
}
class _RecordPageState extends State<RecordPage> {
#override
Widget build(BuildContext context) {
//.....
}
}
How can I access recordObject inside _RecordPageState?
To use recordObject in _RecordPageState, you have to just write widget.objectname like below
class _RecordPageState extends State<RecordPage> {
#override
Widget build(BuildContext context) {
.....
widget.recordObject
.....
}
}
Full Example
You don't need to pass parameters to State using it's constructor.
You can easily access these using widget.myField.
class MyRecord extends StatefulWidget {
final String recordName;
const MyRecord(this.recordName);
#override
MyRecordState createState() => MyRecordState();
}
class MyRecordState extends State<MyRecord> {
#override
Widget build(BuildContext context) {
return Text(widget.recordName); // Here you direct access using widget
}
}
Pass your data when you Navigate screen :
Navigator.of(context).push(MaterialPageRoute(builder: (context) => MyRecord("WonderWorld")));
class RecordPage extends StatefulWidget {
final Record recordObject;
RecordPage({Key key, #required this.recordObject}) : super(key: key);
#override
_RecordPageState createState() => new _RecordPageState(recordObject);
}
class _RecordPageState extends State<RecordPage> {
Record recordObject
_RecordPageState(this. recordObject); //constructor
#override
Widget build(BuildContext context) {. //closure has access
//.....
}
}
example as below:
class nhaphangle extends StatefulWidget {
final String username;
final List<String> dshangle;// = ["1","2"];
const nhaphangle({ Key key, #required this.username,#required this.dshangle }) : super(key: key);
#override
_nhaphangleState createState() => _nhaphangleState();
}
class _nhaphangleState extends State<nhaphangle> {
TextEditingController mspController = TextEditingController();
TextEditingController soluongController = TextEditingController();
final scrollDirection = Axis.vertical;
DateTime Ngaysx = DateTime.now();
ScrollController _scrollController = new ScrollController();
ApiService _apiService;
List<String> titles = [];
#override
void initState() {
super.initState();
_apiService = ApiService();
titles = widget.dshangle; //here var is call and set to
}
I have to Navigate back to any one of the screens in the list pages but when I did that my onTap function stops working and navigation stops.
class MyBar extends StatefulWidget {
MyBar({this.pageNumber});
final pageNumber;
static const String id = 'mybar_screen';
#override
_MyBarState createState() => _MyBarState();
}
class _MyBarState extends State<MyBar> {
final List pages = [
NotificationScreen(),
AppointmentScreen(),
RequestBloodScreen(),
ProfileScreen(),
];
#override
Widget build(BuildContext context) {
var _selectedItemIndex = widget.pageNumber;
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
elevation: 0,
backgroundColor: Colors.white,
unselectedItemColor: Colors.grey.shade700,
selectedItemColor: Color(kAppColor),
selectedIconTheme: IconThemeData(color: Color(kAppColor)),
currentIndex: _selectedItemIndex,
type: BottomNavigationBarType.fixed,
onTap: (int index) {
setState(() {
_selectedItemIndex = index;
});
},
You should use a Pub/Sub mechanism.
I prefer to use Rx in many situations and languages. For Dart/Flutter this is the package: https://pub.dev/packages/rxdart
For example, you can use a BehaviorSubject to emit data from widget A, pass the stream to widget B which listens for changes and applies them inside the setState.
Widget A:
// initialize subject and put it into the Widget B
BehaviorSubject<LiveOutput> subject = BehaviorSubject();
late WidgetB widgetB = WidgetB(deviceOutput: subject);
// when you have to emit new data
subject.add(deviceOutput);
Widget B:
// add stream at class level
class WidgetB extends StatefulWidget {
final ValueStream<LiveOutput> deviceOutput;
const WidgetB({Key? key, required this.deviceOutput}) : super(key: key);
#override
State<WidgetB> createState() => _WidgetBState();
}
// listen for changes
#override
void initState() {
super.initState();
widget.deviceOutput.listen((event) {
print("new live output");
setState(() {
// do whatever you want
});
});
}
In my app, often instead of using stateful widgets, I use mainly ChangeNotifierProvider<T> in main.dart, some model class
class FooModel extends ChangeNotifier {
var _foo = false;
void changeFooState() {
_foo = true;
notifyListeners();
}
bool getFoo () => _foo;
}
and
var foo = context.read<FooModel>();
# or
var foo = context.watch<FooModel>();
in my stateless widgets. IMO this gives me more precise control over the rebuilding upon runtime state change, compared to stateful widgets.
The recipe can be found in the official docs, the concept is called "lifting state up".