"not found" when reload page on flutter web - flutter

On flutter web when I reload a page on Chrome I get the text "not found". How can I fix it? this is my code of the main.dart. I also noticed that to get directly to a page I have to insert an hash symbol (#) in the url like this: "http://127.0.0.1:8080/#/homepage". Is there a way to remove it?
class MyApp extends StatefulWidget {
const MyApp({Key key}): super(key: key);
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
// This widget is the root of your application.
#override
void initState() {
html.window.history.pushState(null, "Home", "/");
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
initialRoute: "/",
theme: ThemeData(
primarySwatch: Colors.blue,
fontFamily: 'GoogleSansRegular'
),
routes: {
"/": (context) => HomePage(),
"/homepage": (context) => HomePage(),
"/secondPage": (context) => SecondPage()
},
);
}
}

to remove the # in the URL you have to switch to set the UrlStrategy, like it´s descriped here: https://docs.flutter.dev/development/ui/navigation/url-strategies
Long Story short: Add this package (https://pub.dev/packages/url_strategy) to pubspec.yaml and call setPathUrlStrategy() in your main method:
import 'package:url_strategy/url_strategy.dart';
void main() {
// Here we set the URL strategy for our web app.
// It is safe to call this function when running on mobile or desktop as well.
setPathUrlStrategy();
runApp(MyApp());
}
Maybe it also solves your other problem. If not, then i think it´s a good idea to use the AutoRoute package: https://pub.dev/packages/auto_route

Related

Flutter Navigator popUntil method doesn't stop at the Named Route I want it to, and pops all the screen

I have this bit of code that I want to pop all screens until it gets to the base screen with Name "/".
Navigator.of(context).popUntil(ModalRoute.withName("/"));
Before I call the popUntil method, I navigated using:
Navigator.of(context).pushNamed("/Loading");
Navigator.of(context).pushReplacementNamed("/Menu");
But the result I'm getting is all the screens are getting popped until it gets to the black screen. What should I change to make it stop at "/"?
Here is how it's set up:
main.dart
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
onGenerateRoute: AppRouter().onGenerateRoute,
initialRoute: '/',
),
);
}
}
class AppRouter {
Route? onGenerateRoute(RouteSettings routeSettings) {
switch (routeSettings.name) {
case '/':
return MaterialPageRoute(builder: (_) => const LoadingScreen());
case '/Menu':
return MaterialPageRoute(builder: (_) => const MenuScreen());
case '/Loading':
return MaterialPageRoute(builder: (_) => const LoadingScreen());
}
}
}
The ModalRoute.withName predicate is used when a route is tied to a specific route name. Because you're using onGenerateRoute (which is typically a last resort) instead of the routes table in your MaterialApp there is no route associated with the / route name.

Hello. I'm building an app and I'm trying to avoid my onboarding screen showing at every startup. I want it to show only once per user

This is my main.dart file:
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
configOneSignal();
}
void configOneSignal() {
OneSignal.shared.init(kAppID);
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<ThemeChanger>(
create: (_) => ThemeChanger(),
child: Builder(builder: (context) {
final themeChanger = Provider.of<ThemeChanger>(context);
return MaterialApp(
debugShowCheckedModeBanner: false,
title: TITLE,
themeMode: themeChanger.getTheme,
darkTheme: Style.get(true),
theme: Style.get(false),
home: Directionality(
textDirection: textDirection,
child: OnboardingScreenOne(),
),
);
}),
);
}
}
I need help making sure that my splash screen doesnt come up to every user on every startup. So I want to have it show only once per user per device.
You can use the shared_preferences package to store a isFirstTime boolean variable. Set it to true in the beginning. When the user finishes the onboarding for the first time, set it to false. And when loading the app, you can check if isFirstTime is true; if it is true, show onboarding, otherwise skip to home.
you need to use shared_preferences to record user behavior, once the user navigator after onboarding pages, record it locally, then determine by retrieving the value back when open app:
home: Directionality(
textDirection: textDirection,
child: isRead? HomePage() : OnboardingScreenOne(),

Reload widget when pop from initialRoute

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(),
),
);
}
}

Should flutter root widget always be StatelessWidget?

When I read the doc in flutter, I have a question that should flutter root widget always be StatelessWidget?
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Code Sample for Navigator',
// MaterialApp contains our top-level Navigator
initialRoute: '/',
routes: {
'/': (BuildContext context) => HomePage(),
'/signup': (BuildContext context) => SignUpPage(),
},
);
}
}
Because I think there's sometime need init function to call, and maybe not want the code of that write in HomePage. For example: check token expire or not, and decide go to HomePage or LoginPage.
Then the best option: should I change the root Widget to StatefulWidget, and just include the logic above in its initState function ?
Making root widget a StatefulWidget is useful when listen AppLifecycleState
such as do resume job like resume WebSocket connection
code snippet
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
AppLifecycleState _lastLifecycleState;
#override
void initState() {
super.initState();
initPlatformState();
WidgetsBinding.instance.addObserver(this);
}
initPlatformState() async {
Screen.keepOn(true);
}
Future<void> resumeCallBack() {
if (sl<WebSocketService>().webSocketState == 'lost') {
sl<WebSocketService>().initWebSocket();
}
if (mounted) {
setState(() {});
}
print("resumeCallBack");
}
Future<void> suspendingCallBack() {
if (mounted) {
setState(() {});
}
print("suspendingCallBack");
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
print("AppLifecycleState Current state = $state");
setState(() {
_lastLifecycleState = state;
});
switch (state) {
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
case AppLifecycleState.detached:
/*case AppLifecycleState.suspending:
await suspendingCallBack();
break;*/
case AppLifecycleState.resumed:
await resumeCallBack();
break;
}
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Template',
debugShowCheckedModeBanner: false,
theme: new ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/',
routes: {
'/': (context) => LoginPage(),
Yes, you can use StatefulWidget as your root parent. But, you should only use it when it makes sense.
Like, If you are initialising and observing some animations, firebase messaging, services, applifecycle states etc. which might require sometimes.
Otherwise Stateless widget are better to use.
It can be StatefulWidget or StatelessWidget. But placing a StatefulWidget in the root will impact your app performance because a simple state change in the StatefulWidget will cause the entire widget tree to rebuild and reduce your app performance.
always try to place StatefulWidget or InheritedWidget deep inside the widget tree.
better solution for your scenario is to use Providers or InheritedWidgets and listen to the token changes rather than changing the root widget to Stateful

Flutter pass argument directly with named routes navigation

I have been looking at all the answers on here to pass arguments when doing named route navigation but they seem to be old answers or they don't work.
From what was written it should be working but it doesn't seem to do anything, so I am not sure where my error is.
This is how I have it setup:
Main.dart (With my named routes setup):
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: Colors.white,
),
initialRoute: HomePageScreen.id,
routes: {
HomePageScreen.id: (context) => HomePageScreen(),
AddItemScreen.id: (context) => AddItemScreen(),
AdvertiseScreen.id: (context) => AdvertiseScreen(),
HomePageFilterScreen.id: (context) => HomePageFilterScreen(),
HomePageResultsScreen.id: (context) => HomePageResultsScreen(),
ItemPageProfileScreen.id: (context) => ItemPageProfileScreen(),
ItemPageProfileSuggestUpdateScreen.id: (context) => ItemPageProfileSuggestUpdateScreen(),
ItemPageWhereToBuyAddStoreToDatabaseScreen.id: (context) => ItemPageWhereToBuyAddStoreToDatabaseScreen(),
ItemPageWhereToBuyMapScreen.id: (context) => ItemPageWhereToBuyMapScreen(),
ItemPageWhereToBuyScreen.id: (context) => ItemPageWhereToBuyScreen(),
MenuScreen.id: (context) => MenuScreen(),
NotAvailableScreen.id: (context) => NotAvailableScreen(),
TermsScreen.id: (context) => TermsScreen(),
}
);
}
}
HomePageResultsScreen.dart (On button click I am using push named to navigate to the next page, this is working because the new page 'ItemPageProfileScreen is opening):
onTap: () {
Navigator.pushNamed(context, ItemPageProfileScreen.id, arguments: 'MyTestString');
}
ItemPageProfileScreen.dart (I have tried using MaterialApp onGenerateRoute to get the arguments and print to screen to test but it is not working):
class ItemPageProfileScreen extends StatefulWidget {
static const String id = 'item_page_profile_screen';
#override
_ItemPageProfileScreenState createState() => _ItemPageProfileScreenState();
}
class _ItemPageProfileScreenState extends State<ItemPageProfileScreen> {
#override
Widget build(BuildContext context) {
MaterialApp(
onGenerateRoute: (routeSettings){
final arguments = routeSettings.arguments;
print(arguments.toString());
},
);
return Scaffold(),
Thanks for your help.
EDIT Second attempt:
class ItemPageProfileScreen extends StatefulWidget {
final String argument;
ItemPageProfileScreen(this.argument);
static const String id = 'item_page_profile_screen';
#override
_ItemPageProfileScreenState createState() => _ItemPageProfileScreenState();
}
class _ItemPageProfileScreenState extends State<ItemPageProfileScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Text(widget.argument),
There is an official article on how to pass arguments with named routing. https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments
The main idea is pretty straightforward: pass arguments into the constructor of your screen widget.
In the official docs (in the link above) they actually used both approaches with named routing and with regular routing even though the article stated about named routing.
Anyways. Focus on the constructor and arguments.
Where can you access the constructor of your screen with named routing if you pass only the name of the route when you navigate? In onGenerateRoute method. Let's do it.
Overwrite onGenerateRoute method in your top screen MyApp (that's where your mistake was). And if you do it you don't need routes: {} there (your second mistake)
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: Colors.white,
),
initialRoute: HomePageScreen.id,
onGenerateRoute: (settings) {
if(settings.name == ItemPageProfileScreen.id) {
String msg = settings.arguments;
return MaterialPageRoute(builder: (_) => ItemPageProfileScreen(msg));
} else if(...
},
Get the arguments from the widget constructor:
class ItemPageProfileScreen extends StatefulWidget {
final String argument;
ItemPageProfileScreen(this.argument);
static const String id = 'item_page_profile_screen';
#override
_ItemPageProfileScreenState createState() => _ItemPageProfileScreenState();
}
class _ItemPageProfileScreenState extends State<ItemPageProfileScreen> {
#override
Widget build(BuildContext context) {
String msg = widget.argument;
...
And sending arguments over on tap:
onTap: () {Navigator.pushNamed(context, ItemPageProfileScreen.id, arguments: 'MyTestString');}
Hope this helps.