I just starting to learn flutter and want to implement login page.
So i check if i have token or not then decide if it is my home route or login route to be initialized as initialRoute, the problem is when flutter render login route, it also requesing API i declared on home route which is gonna return empty list because i dont have any token yet.
Future<void> main() async{
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
var status = prefs.getString('jwt') ?? "";
if(status!="")
runApp(HomeR(initialRoute: "/"));
else
runApp(HomeR(initialRoute: "/login"));
}
class HomeR extends StatelessWidget {
static const routeName = '/';
HomeR({Key key, this.initialRoute}) : super(key: key);
String initialRoute = "/";
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'blablabal',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'blablabla'),
initialRoute: initialRoute,
routes: {
loginR.routeName: (context) => loginR(),
},
);
}
}
and use Navigator.pop(context) from login button after authenticate first but my home route doesnt refresh itself and display nothing, since it is using empty list before.
How to tell home route to reload when i pop from login route?
This is MaterialApp:
MaterialApp(
// no need for home
title: 'blablabal',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: {
'/': (context) => InitialApp(),
loginR.routeName: (context) => loginR(),
},
initialRoute: '/',
),
and In InitialApp:
class InitialApp extends StatefulWidget {
#override
_InitialAppState createState() => _InitialAppState();
}
class _InitialAppState extends State<InitialApp> {
void loginLogic() async
{
SharedPreferences.getInstance().then((value){
var status = value.getString('jwt') ?? "";
if(status=='')
{
Navigator.pushReplacementNamed(context, loginR.routeName);
}
else //logged in
{
Navigator.pushReplacementNamed(context, LaporanList.routeName);
}
});
}
#override
void initState() {
loginLogic();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
Related
I made a todo app using Getx package. I also created a login page and I want to display the login page only one time but when I try to do this I a got error
"HomeController" not found. You need to call Get.put(HomeController()) or Get.lazyPut(()=>HomeController()).
this is my binding
class HomeBinding implements Bindings {
#override
void dependencies() {
Get.lazyPut(() => HomeController(
taskRepository: TaskRepository(
taskProvider: TaskProvider(),
),
));
}
}
this is my main.dart
int? isViewed;
void main() async {
await GetStorage.init();
await Get.putAsync(() => StorageService().init());
WidgetsFlutterBinding.ensureInitialized();
await ScreenUtil.ensureScreenSize();
await GetStorage.init();
await Get.putAsync(() => StorageService().init());
LicenseRegistry.addLicense(() async* {
final license = await rootBundle.loadString('google_fonts/OFL.txt');
yield LicenseEntryWithLineBreaks(['google_fonts'], license);
});
SharedPreferences prefs = await SharedPreferences.getInstance();
isViewed = prefs.getInt('login');
runApp(MyApp());
}
class MyApp extends GetView<HomeController> {
const MyApp({
Key? key,
}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(statusBarColor: Colors.transparent));
return ScreenUtilInit(
designSize: const Size(360, 800),
minTextAdapt: true,
splitScreenMode: true,
builder: (context, child) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: Themes.light,
darkTheme: Themes.dark,
themeMode: ThemeMode.light,
home: isViewed != 0 ? Login() : Report(),
initialBinding: HomeBinding(),
builder: EasyLoading.init(),
);
});
}
}
This is the error
Try adding HomeBinding in getPages array of GetMaterialApp.
Also apply below modifications in below scope
GetMaterialApp(
// initialBinding: HomeBinding(), ====>> Remove this line
initialRoute: '/home', // ====>> Add this line
getPages: [
GetPage(
name: "/home",
page: () => const HomeScreen(),
binding: HomeBinding(),
),
// ====>> Add other pages like home
]
);
I choose to use provider as my state management so I saw I have to use Multi provider.
My struggle is how to architect my code that I can initialize all the data I need when my app first run and give the providers to the multi provider.
Provider example
import 'package:cron/cron.dart';
import 'package:flutter/material.dart';
import 'package:web_app/models/fixture.dart';
import 'package:web_app/services/fixture_service.dart';
class HighlightsProvider extends ChangeNotifier {
final List<Fixture> _highlights = [];
List<Fixture> get() => _highlights;
Future<void> fetchHighlights() async {
try {
List<Fixture> highlightFixtures = [];
final response = await FixtureService().getAppHighlightFixtures();
[...response].asMap().forEach((index, element) {
highlightFixtures.add(new Fixture.fromJson(element));
});
_highlights.clear();
_highlights.addAll(highlightFixtures);
notifyListeners();
} catch (e) {
print('error');
print(e);
}
}
runJob(cron) {
cron.schedule(Schedule.parse('* * * * *'), () async {
fetchHighlights();
print('fetch highlights every one minute');
});
}
}
Let's say this class will get all my providers and initialize theme:
class InitializeApp {
final cron = Cron();
Future run(HighlightsProvider highlightsProvider) async {
return Future.wait([
initiakizeHighlights(highlightsProvider),
]);
}
Future initiakizeHighlights(HighlightsProvider highlightsProvider) async {
highlightsProvider.runJob(cron);
await highlightsProvider.fetchHighlights();
}
}
Then I have to deliver those provider to the multi provider:
void main() async {
final highlightsProvider = HighlightsProvider();
await InitializeApp().run(highlightsProvider);
print('ready');
runApp(MyApp(highlightsProvider: highlightsProvider));
}
class MyApp extends StatelessWidget {
final highlightsProvider;
const MyApp({Key key, this.highlightsProvider}) : super(key: key);
#override
Widget build(BuildContext context) {
print('build');
return MultiProvider(
providers: [
ChangeNotifierProvider<HighlightsProvider>.value(
value: highlightsProvider,
)
],
child: MaterialApp(
title: 'tech',
theme: ThemeData(
primarySwatch: Colors.amber,
brightness: Brightness.light,
),
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return MyHomePage(title: 'Flutter Demo Home Page');
}
}),
);
}
}
Normally you just wrap your MaterialApp with the MultiProvider, then you already have access to all Providers you will define.
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<RecipeStreamService>.value(value: RecipeStreamService().controllerOut)
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Home Food',
routes: {
'/register': (BuildContext context) => RegisterPage(),
'/login': (BuildContext context) => LoginPage()
},
),
home: HomePage(title: 'Home'),
),
);
}
I am trying to create a simple authentication flow using Provider. I have three pages :
LoginPage
OnboardingPage
HomePage
The flow of this app is:
if a user opens the app for the first time, he/she will be redirected to the onboarding then to login to home.
For the second time user, the app first checks the login status and redirected to either log in -> home or straight to home page.
Here is my setup in code :
main.dart
void main() {
runApp(MultiProvider(providers: [
ChangeNotifierProvider<StorageHelper>(create: (_) => StorageHelper()),
ChangeNotifierProvider<AuthProvider>(create: (_) => AuthProvider()),
], child: MyApp()));
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return Consumer<AuthProvider>(builder: (final BuildContext context,
final AuthProvider authProvider, final Widget child) {
print(authProvider.isAuthenticated); // this is false whenever I //click the logout from category(or other pushed pages) but the below ternary //operation is not executing
return MaterialApp(
title: 'My Poor App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Color(0xff29c17e),
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: authProvider.isAuthenticated ? HomeScreen() : LoginScreen(),
onGenerateRoute: Router.onGenerateRoute,
);
});
}
}
LoginScreen.dart
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
final authProvider = Provider.of<AuthProvider>(context, listen: false);
return Scaffold(
body: Center(
child: MaterialButton(
onPressed: () async {
await authProvider.emailLogin('user#email.com', 'pass');
},
child: Text('Login'))),
);
}
}
HomeScreen.dart
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
final auth = Provider.of<AuthProvider>(context, listen: false);
return Scaffold(
body: Center(
child: MaterialButton(
elevation: 2,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CategoryScreen()));
},
child: Text('Reset')),
),
);
}
}
AuthProvider.dart
class AuthProvider extends ChangeNotifier {
bool _isAuthenticated = false;
bool get isAuthenticated => _isAuthenticated;
set isAuthenticated(bool isAuth) {
_isAuthenticated = isAuth;
notifyListeners();
}
Future emailLogin(String email, String password) async {
isAuthenticated = true;
}
Future logout() async {
isAuthenticated = false;
}
}
If i logout from home page using Provider.of<AuthProvider>(context).logout() it works fine. But if I push or pushReplacement a new route and try to logout from the new route (just say I navigated from home to category page and try to logout from there), I am not redirected to LoginPage. If I print the value of isAuthenticated it prints false but the consumer is not listening or at least not reacting to the variable change.
Please don't mark this question as duplicate, I have searched many other similar questions and none of them worked for my case.
Edit:
CategoryScreen.dart
class CategoryScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () {
final auth = Provider.of<AuthProvider>(context, listen: false);
auth.logout();
// print(auth.isAuthenticated);
},
child: Text('Category Logout'),
),
),
);
}
}
I guess your problem is that you did not use Consumer for the logout, in your home in the MaterialApp. Just see, that if it works out for you
main.dart
// needs to listen to the changes, to make changes
home: Consumer<AuthProvider>(
builder: (context, authProvider, child){
return authProvider.isAuthenticated ? HomeScreen() : LoginScreen();
}
)
Since, Consumer was not there for your home, even if the value was being changed, it was not able to work on updating the view for you as per the Provider.
I am trying to implement custom logout solution for my application, where no matter where user currently is, once the Logout button is clicked, app will navigate back to Login page.
My idea was, that instead of listening on every component for state changes, I would have one single listener on a master component -> MyApp.
For the sake of simplicity, I have stripped down items to bare minimum. Here is how my Profile class could look like:
class Profile with ChangeNotifier {
bool _isAuthentificated = false;
bool get isAuthentificated => _isAuthentificated;
set isAuthentificated(bool newVal) {
_isAuthentificated = newVal;
notifyListeners();
}
}
Now, under Main, I have registered this provider as following:
void main() => runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => Profile(),
)
],
child: MyApp(),
),
);
And finally MyApp component:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return Consumer<Profile>(
builder: (context, profile, _) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Color.fromARGB(255, 0, 121, 107),
accentColor: Color.fromARGB(255, 255, 87, 34),
),
home: buildBasePage(context, profile),
);
},
);
}
Widget buildBasePage(BuildContext context, Profile currentProfile) {
return !currentProfile.isAuthentificated
? LoginComponent()
: MyHomePage(title: 'Flutter Demo Home Page test');
}
}
My idea was, that as MyApp component is the master, I should be able to create a consumer, which would be notified if current user is authentificated, and would respond accordingly.
What happens is, that when I am in e.g. MyHomePage component and I click Logout() method which looks like following:
void _logout() {
Provider.of<Profile>(context, listen: false).isAuthentificated = false;
}
I would be expecting that upon changing property, the initial MyApp component would react and generate LoginPage; which is not the case. I have tried changing from Consumer to Provider.of<Profile>(context, listen: false) yet with the same result.
What do I need to do in order for this concept to work? Is it even correct to do it this way?
I mean I could surely update my Profile class in a way, that I add the following method:
logout(BuildContext context) {
_isAuthentificated = false;
Navigator.push(
context, MaterialPageRoute(builder: (context) => LoginComponent()));
}
And then simply call Provider.of<Profile>(context, listen: false).logout(), however I thought that Provider package was designed for this...or am I missing something?
Any help in respect to this matter would be more than appreciated.
I don't know why it wasn't working for you. Here is a complete example I built based on your description. It works!
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Profile with ChangeNotifier {
bool _isAuthentificated = false;
bool get isAuthentificated {
return this._isAuthentificated;
}
set isAuthentificated(bool newVal) {
this._isAuthentificated = newVal;
this.notifyListeners();
}
}
void main() {
return runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<Profile>(
create: (final BuildContext context) {
return Profile();
},
)
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(final BuildContext context) {
return Consumer<Profile>(
builder: (final BuildContext context, final Profile profile, final Widget child) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: profile.isAuthentificated ? MyHomePage() : MyLoginPage(),
);
},
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(final BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Home [Auth Protected]")),
body: Center(
child: RaisedButton(
child: const Text("Logout"),
onPressed: () {
final Profile profile = Provider.of<Profile>(context, listen: false);
profile.isAuthentificated = false;
},
),
),
);
}
}
class MyLoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Login")),
body: Center(
child: RaisedButton(
child: const Text("Login"),
onPressed: () {
final Profile profile = Provider.of<Profile>(context, listen: false);
profile.isAuthentificated = true;
},
),
),
);
}
}
You don't need to pass listen:false, instead simply call
Provider.of<Profile>(context).logout()
So your Profile class would look like
class Profile with ChangeNotifier {
bool isAuthentificated = false;
logout() {
isAuthentificated = false;
notifyListeners();
}
}
I want to make an app which connects to server and sends it different requests, gets responses etc. For that I have a Client class (not a widget), which handles all the network stuff. I use bloc pattern to marshal requests to Client and get back responses. This Client instance should be accessible all over the widget tree, so I use InheritedWidget. My idea is that the first thing user sees after opening an app is splash screen, which should change to some other page when connection to server is established. I subscribe to response stream in SplashScreen's build method and navigate to different page when response is received, but for some reason accessing InheritedWidget triggers rebuild of SplashScreen which leads to accessing InheritedWidget which leads to rebuild and so on and so forth. And obviously stream starts complaining that I already subscribed to it. I do not change InheritedWidget anywhere, and updateShouldNotify is not called, and I set it to return false anyway. Here is minimal reproducible example. It obviously does not perform any real network communication, but it demonstrates what I try to say.
var client = ClientInheritedData.of(context).client leads to rebuild. If I comment it and lines that use client, rebuild is not triggered.
class Client {
StreamController<int> _eventStreamController;
StreamSink<int> get eventSink => _eventStreamController.sink;
Stream<int> _eventStream;
StreamController<String> _responseStreamController;
StreamSink<String> _responseSink;
Stream<String> get responseStream => _responseStreamController.stream;
Client() {
_eventStreamController = StreamController();
_eventStream = _eventStreamController.stream;
_responseStreamController = StreamController<String>();
_responseSink = _responseStreamController.sink;
_eventStream.listen((event) async {
if (event == 1) {
await Future.delayed(Duration(seconds: 2)); // simulate some work
_responseSink.add('Connected!');
}
});
}
}
void main() => runApp(ClientInheritedData(Client(), MyApp()));
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.green,
),
initialRoute: '/',
onGenerateRoute: RouteGenerator.generate,
);
}
}
class SplashScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
var client = ClientInheritedData.of(context).client;
client.eventSink.add(1);
client.responseStream.listen((response) {
Navigator.of(context).pushNamed('/Sign Up');
});
return Scaffold(
appBar: AppBar(
title: Text('Splash Screen'),
),
body: Center(
child: Text('Greetings!'),
),
);
}
}
class SignUp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sign Up'),
),
body: Center(
child: Text('Hey man'),
),
);
}
}
class ClientInheritedData extends InheritedWidget {
final Client client;
const ClientInheritedData(this.client, Widget child) : super(child: child);
static ClientInheritedData of(BuildContext context) {
return context.inheritFromWidgetOfExactType(ClientInheritedData) as ClientInheritedData;
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) {
return false;
}
}
class RouteGenerator {
static Route<dynamic> generate(RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(
builder: (context) => SplashScreen(),
);
case '/Sign Up':
return MaterialPageRoute(
builder: (context) => SignUp(),
);
}
}
}