There's the question – could I create class instance if I have string variable which contains its name?
For example, I have
var className = 'DocumentsList';
could I do something like this
var docListWidget = createInstance(className[, params]);
Flutter (and Dart) do not have the facilities to do that. On purpose. They implement tree shaking, a mechanism to remove unused code to make your app smaller and faster. However, to do that, the compiler has to know what code is used and what is not. And it cannot possibly know what code gets used if you can do stuff like you describe.
So no, this is not possible. Not with that degree of freedom. You can have a big switch statement to create your classes based on strings, if you know in advance which string it will be. That is static, the compiler can work with that.
What you want is called "reflection" and you can add some capabilities using packges like reflectable or mirror but they cannot change the compilation process, they too work through the fact that you specify beforehand which classes need reflection. A totally dynamic usage is just not possible (on purpose).
Since you mentioned the routing table: You cannot create a class from a string, you can however create the string form the class:
import 'package:flutter/material.dart';
typedef Builder<T> = T Function(BuildContext context);
String routeName<T extends Widget>() {
return T.toString().toLowerCase();
}
MapEntry<String, Builder<Widget>> createRouteWithName<T extends Widget>(Builder<T> builder) {
return new MapEntry(routeName<T>(), (context) => builder(context));
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: routeName<ScreenPicker>(),
routes: Map.fromEntries([
createRouteWithName((context) => ScreenPicker()),
createRouteWithName((context) => ScreenOne()),
createRouteWithName((context) => ScreenTwo()),
]),
);
}
}
class ScreenPicker extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("Navigate a route")),
body: Column(children: [
RaisedButton(
child: Text('One'),
onPressed: () => Navigator.pushNamed(context, routeName<ScreenOne>())),
RaisedButton(
child: Text('Two'),
onPressed: () => Navigator.pushNamed(context, routeName<ScreenTwo>())),
]));
}
}
class ScreenTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("Second Screen")), body: Center(child: Text("Two")));
}
}
class ScreenOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("First Screen")), body: Center(child: Text("One")));
}
}
This way you have no strings with route names in your project that can be changed or mistyped or forgotten when renaming something.
The best way to do that is using a named constructor like:
class MyClass {
const MyClass.create();
}
So the way you do this is passing the Type calling the method within. Remember you can pass the function without calling it, like:
Get.lazyPut(MyClass.create);
If you really need to use String for identifying the class type I suggest you create some mapping for it.
There's some functions from rootBundle that can be useful in this situation, like rootBundle.loadString() that can be done to load code from files.
Hope this was helpful.
Related
I'm new to flutter and collecting fragments of code from here and there.
I have a class that holds some data that will be passed later to a widget and generate the UI based on the passed data
class OnBoardingViewModel with ChangeNotifier {
final List<OnBoardingPageContent> onBoardingPages = [
OnBoardingPageContent(
image: ImageManager.onBoardingImage1,
title: StringManager.onBoardingTitle1,// I want the text to be localized
subtitle: StringManager.onBoardingSubTitle1,
),
OnBoardingPageContent(
image: ImageManager.onBoardingImage2,
title: StringManager.onBoardingTitle2,
subtitle: StringManager.onBoardingSubTitle2,
)
];
... some other code
the strings above are hardcoded with specific language I wanted them to be localized
the localization require
S.of(context).yourKey
I thought of passing the key as a string and in UI build (where I will have the context)
to do
`S.of(context)[thePassedKeyStringVar]` // but that is wrong in Dart
I tried to pass the context to my Class OnBoardingViewModel so I could have
class OnBoardingViewModel with ChangeNotifier {
static BuildContext context;
final List<OnBoardingPageContent> onBoardingPages = [
OnBoardingPageContent(
title: S.of(context).onBoardingText1,
but it did not end well with me
here is the entry point of my app
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => OnBoardingViewModel(_),
),
...
],
child: const MyApp(),
),
);
}
any help on how to localize the strings in my static list in the above class?
Short answer : That is not possible with the library you're using (it is a strictly static-only localization library).
Detailed answer :
The way I handle translations when having to manipulate datas in a Controller/Presenter/ViewModel (call it whatever you want), is by returning the key to translate in my model.
You should be able to do what you want with the easy_localization package
/assets/translations/en.json
{[
'my_key': 'The english value',
]}
class AccountController with ChangeNotifier {
AccountObject accountObj = AccountObject();
void updateObject() {
accountObj.title = "my_key";
notifyListeners();
}
}
class AccountWidget extends StatelessWidget {
const AccountWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<AccountController>(
builder: (context, accountController, child) {
return Center(
child: Text(accountController.accountObj.title).tr
);
}
);
}
}
Another good practice is to keep your Widgets outside from your Controllers.
Use your Controllers to build the data and notify the Widget Tree when it should re-build (and keep your Widgets in your Widgets tree) :)
Also, keep in mind that your Context should stay in your Widgets (it'll save you from a lot of errors/weird behaviors :))
Is it true that Flutter encourages inline event handlers because it is the only way to obtain the BuildContext?
e.g.,
class X extends StatelessWidget {
handler() {
//cannot use this if I need the BuildContext
}
Widget build(BuildContext ctx) {
return Scaffold(
home: TextButton(
text: Text("Click me"),
onPressed: () { //must be inline, cannot reference "handler" because need "ctx"
Scaffold.of( ctx ).showSnackBar(/*...*/);
}
)
)
}
}
Most languages encourage simplifying the code by moving event handling code away from the UI but with Flutter, if BuildContext object is needed, there is no "pretty" way to do it except to put the handler inline.
Have I mistaken?
Most languages encourage simplifying the code by moving event handling code away from the UI
Actually, it seems like the way the industry is moving is towards this declarative "component" model; we have SwiftUI, React, Jetpack Compose etc.
Part of the attraction of declarative UI is the fact that the hierarchy of the code matches the hierarchy of the created widgets in the UI. The syntax of Dart makes this quite nice, for example you can rewrite your build method to (using Flutter 2.5.2):
class X extends StatelessWidget {
Widget build(BuildContext context) => Scaffold(
body: TextButton(
child: Text("Click me"),
onPressed: () => Scaffold.of(context).showSnackBar(SnackBar(
content: Text("You clicked me!"),
)),
),
);
}
and the indentation provides a good visual representation of how the elements are nested in the final UI.
Now, you aren't wrong for being concerned about excessive in-lining, but the declarative way of dealing with this seems to be splitting a component up into sub-components. For example, with your X widget, the TextButton could be broken out into a specialised component:
class X extends StatelessWidget {
Widget build(BuildContext context) => Scaffold(
body: SnackButton(),
);
}
class SnackButton extends StatelessWidget {
Widget build(BuildContext context) => TextButton(
child: Text("Click me"),
onPressed: () => Scaffold.of(context).showSnackBar(SnackBar(
content: Text("You clicked me!")
)),
);
}
This still keeps the onPressed handler near the item it is acting on, so readers of the code don't need to jump around looking for the definition of the handler.
But why did Flutter design it that way in the first place? Why didn't they make all event handlers (onPress, onTap) pass in the context by default?
I can see a through-line from the choice of declarative UI to expecting that everything that needs a BuildContext will be contained in-line in the build method, as this way it's all declared as it will be laid out, without having to look elsewhere. It is definitely a trade-off (as are all things), but I think if you are sensible about the implementation, and look for places to split off components or groups of components, this won't be as annoying as you are finding it now.
It should be something like that. Pass ctx to functions to have access.
class X extends StatelessWidget {
handler(BuildContext ctx) {
Scaffold.of( ctx ).showSnackBar(/*...*/);
}
Widget build(BuildContext ctx) {
return Scaffold(
home: TextButton(
text: Text("Click me"),
onPressed: () => handler(ctx)
)
)
}
}
Edit, just like #msbit explained
I am unable to access a public static boolean from a different class, eg. I have a boolean isFull in my StudyjiosListviewScreen class as shown:
class StudyjiosListviewScreen extends StatefulWidget {
#override
_StudyjiosListviewScreenState createState() => _StudyjiosListviewScreenState();
}
class _StudyjiosListviewScreenState extends State<StudyjiosListviewScreen> {
static bool isFull = false;
...
I want to use this boolean isFull in another class JoinStudyjio.
I created an instance of the StudyjiosListviewScreen class in the JoinStudyjio class like this:
StudyjiosListviewScreen listviewScreen = StudyjiosListviewScreen();
But when I try to use the boolean isFull like this:
if (listviewScreen.isFull) {
...
I get an error. I have already imported the file for the StudyjiosListviewScreen class inside the file for the JoinStudyjio class.
This is because StudyjiosListviewScreen and _StudyjiosListviewScreenState are 2 different classes.
The static variable isFull which you are trying to access is of the later one and you are trying to access it by creating an instance of the first one. If it had been a static variable of the class StudyjiosListviewScreen, you could have accessed it without even creating an instance of that class like this StudyjiosListviewScreen.isFull
If I understood your issue correctly, and following the suggestion I made in my comment, here is a code example of sharing a variable and a method to change it's value, down to two classes from a parent class:
class VariableSharing62951032 extends StatefulWidget {
#override
_VariableSharing62951032State createState() => _VariableSharing62951032State();
}
class _VariableSharing62951032State extends State<VariableSharing62951032> {
bool isFull = false;
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
ClassA62951032(isFull: isFull, swapIsFull: swapIsFull,),
ClassB62951032(isFull: isFull, swapIsFull: swapIsFull,),
],
);
}
void swapIsFull(){
setState(() {
isFull = !isFull;
});
}
}
class ClassA62951032 extends StatefulWidget {
final bool isFull;
final Function swapIsFull;
ClassA62951032({
this.isFull,
this.swapIsFull
});
#override
_ClassA62951032State createState() => _ClassA62951032State();
}
class _ClassA62951032State extends State<ClassA62951032> {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text('Class A'),
Text(widget.isFull.toString()),
RaisedButton(
child: Text('Swap isFull'),
onPressed: () => widget.swapIsFull(),
),
],
);
}
}
class ClassB62951032 extends StatefulWidget {
final bool isFull;
final Function swapIsFull;
ClassB62951032({
this.isFull,
this.swapIsFull
});
#override
_ClassB62951032State createState() => _ClassB62951032State();
}
class _ClassB62951032State extends State<ClassB62951032> {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text('Class B'),
Text(widget.isFull.toString()),
RaisedButton(
child: Text('Swap isFull'),
onPressed: () => widget.swapIsFull(),
),
],
);
}
}
Sharing variables and methods between classes it's a huge deal in Flutter.
First of all, you are passing it in the wrong way. That variable is saved in your state widget, which is defined as private.
So, or you define it as public and than you pass a key associated with your state, or you change complitelly approach. I don't like passing keys and it is not good for production, so I will give you a better example using providers:
add provider library to your pubspec.yaml:
provider: ^4.3.1 // Or latest version
Create a class where you can save that value:
class valuesHelper {
//In this class we are storing global, dynamic values
bool _isSeen;
valuesHelper() {
this._isSeen = false;
}
void setValue(bool value) {
this._isSeen = value;
}
bool getValue(){
return this._isSeen;
}
}
Now wrap your main with the provider and pass the valuesHelper();
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return Provider(
create: (_) => valuesHelper(),
child: MaterialApp(
home: MyHomePage(),
),
);
}
}
Now call the Provider.of(context) wherever you want.
//Somwhere in your code when you have access to context:
ValueHelper helper = Provider.of<valueHelper>(context);
helper.setValue(true);
//Somwhereelse in your code when you have access to context:
ValueHelper helper = Provider.of<valueHelper>(context);
bool theValueIWant = helper.getValue();
If you have asynchronous stuff and huge state managment Blocs are even better and fancier, but for this kind of things Providers are more than enough.
I want to make my code neater but I have a problem when I separate widgets that I use often in 1 file
here is it my main widget
import 'package:a_tiket/Helpers/widget_helper.dart';
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
bool _isLoading = false;
var _message = '';
var _hasError = false;
#override
Widget build(BuildContext context) {
return
_isLoading ?
_loadingWidget(context)
:
Scaffold(
body: SingleChildScrollView(
child: Container(
),
],
),
)
)
)
;
}
}
this is my widget_helper.dart
Widget _loadingWidget (BuildContext context){
return Scaffold(
body: Center(
child: CircularProgressIndicator(
backgroundColor: ACCENT_COLOR,
valueColor: new AlwaysStoppedAnimation<Color>(PRIMARY_COLOR),
),
),
);
}
the problem is i got some error. i have add import for widget_helper but still get error
lib/Pages/loginPage.dart:193:7: Error: The method '_loadingWidget' isn't defined for the class '_LoginPageState'.
what should i do? i just want to make the code neater
please remove underline
change from
_loadingWidget(context)
to
loadingWidget(context)
There are a few issues with your code:
For such a small piece of code like showing a
CircularProgressIndicator you should not be putting a method in a separate
file. Instead of making your code "neater", you are making it harder
to read. If you really want to have it in a separate file, create a Stateless widget that shows the code you want. But then again you are just using a CircularProgressIndicator. You aren't saving any code, just creating more unnecessary code.
You already have a Scaffold where your are going to show the CircularProgressIndicator. You don't need to have another one. It's not doing anything.
While Dart uses camelCase for variable naming, file names use snake_case. Try to use it when naming files.
I am working on an app in Flutter and I'm pretty new to it/Dart. I already created the login, signup etc and everything works perfectly fine. Now I want to create a "Login-Wall" Template for every View that needs the user to be logged in. If the user is not logged in, he should be returned to the LoginView, if the api-call is still loading, it should not show anything but a loading screen called LoadingView(). I started by creating a Stateful Widget called AuthorizedLayout:
class AuthorizedLayout extends StatefulWidget {
final Widget view;
AuthorizedLayout({this.view});
_AuthorizedLayoutState createState() => new _AuthorizedLayoutState();
}
The state utilizes a Future Builder as follows:
Widget build(BuildContext context) {
return FutureBuilder<User>(
future: futureToken,
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return NoConnectionView();
case ConnectionState.active:
case ConnectionState.waiting:
return LoadingView();
case ConnectionState.done:
if(snapshot.data != null) {
print("User Data loaded");
return widget.view;
} else
return LoginView();
}
},
);
}
As you can see, it should load the userdata, and when it's finished it should return the view. The futureToken represents the Future that will return the User-Object from the server after an api-request. In any other case it should show the Loading/Error/Login Page.
I'm calling it like this:
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: AuthorizedLayout(
view: DashboardView(),
),
);
}
In the Build method of the Dashboard view I have a "print('Dashboard View');". The problem I have is that in the output the 'Dashboard View' is printed before the 'User Data Loaded'. That means I can't access the loaded user data in that view. This means that this solution does not work the way I intended it to.
Now for my question: Is there any way I can build this "Login-Wall" and pass the user data to every view that is inside the login wall? I hope the code I posted explains the idea I'm trying to go for.
Is there any way I can build this "Login-Wall" and pass the user data to every view that is inside the login wall?
Absolutely! At a basic level, you're talking about state management. Once a user logs into your app, you want to store that user data so that it's accessible to any widget within the widget tree.
State management in Flutter is a hotly-debated topic and while there are a ton of options, there is no defacto state management technique that fits every app. That said, I'd start simple. One of the simplest and most popular options is the scoped_model package.
You can read all of the details here, but the gist is that it provides utilities to pass a data model from a parent widget to its descendants.
First, install the package.
Second, you'll want to create a model that can hold the user data that you want to be accessible to any widget in the tree. Here's a trivial example of what that might look like:
// user_model.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
class UserModel extends Model {
dynamic _userData;
void setUserData(dynamic userData) {
_userData = userData;
}
String getFirstName() {
return _userData['firstName'];
}
static UserModel of(BuildContext context) =>
ScopedModel.of<UserModel>(context);
}
Next, we'll need to make an instance of this UserModel available to all widgets. A contrived way of doing this would be to wrap your entire app in a ScopedModel. Example below:
// main.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'login_view.dart';
import 'user_model.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<UserModel>(
model: UserModel(),
child: MaterialApp(
theme: ThemeData.light(),
home: LoginView(),
),
);
}
}
In the above code, we're wrapping our entire instance of MaterialApp in a ScopedModel<UserModel>, which will give every widget in the application access to the User model.
In your login code, you could then do something like the following when your login button is pressed:
onPressed() async {
// authenticate your user...
var userData = await someApiCall();
// set the user data in our model
UserModel.of(context).setUserData(userData);
// go to the dashboard
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DashboardView(),
),
);
}
Last but not least, you can then access that user data through the UserModel like so:
// dashboard_view.dart
import 'package:flutter/material.dart';
import 'package:scoped_model_example/user_model.dart';
class DashboardView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: Text(
UserModel.of(context).getFirstName(),
),
),
],
);
}
}
Check out the docs on scoped_model for more details. If you need something more advanced, there are a number of other state management patterns in Flutter such as BloC, Redux, Mobx, Provider and more.
So I just got what was happening. I was passing the already-built widget to the AuthorizedView. What I actually had to pass was a Builder instead of a Widget.
class AuthorizedLayout extends StatefulWidget {
final Builder viewBuilder;
AuthorizedLayout({this.viewBuilder});
_AuthorizedLayoutState createState() => new _AuthorizedLayoutState();
}
Calling it like this:
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: AuthorizedLayout(
viewBuilder: Builder(builder: (context) => DashboardLayout()),
),
);
}
Note that I recalled the final variable to viewBuilder instead of view, compared to the example above.
This will actually build the widget AFTER the userdata is loaded.