At first I implemented l10n from this tutorial to the template project file for Flutter and it was a success.
After that, I tried to move the MyHomePage classes to a new file called home.dart. And it stopped working because when I call Translations.of(context) it returns null. Any difference of the BuildContext when inside main.dart and home.dart?
localization.dart
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Translations {
final Locale locale;
Map<String, dynamic> _messages;
Translations(this.locale);
static Translations of(BuildContext context) => Localizations.of<Translations>(context, Translations);
Future<bool> load() async {
String fileName = 'lang/${locale.languageCode}.json';
String data = await rootBundle.loadString(fileName);
_messages = json.decode(data);
return true;
}
String get(String key) => _messages[key] ?? "** $key not found";
}
class TranslationsDelegate extends LocalizationsDelegate<Translations> {
#override
bool isSupported(Locale locale) => ['en', 'id'].contains(locale.languageCode);
#override
Future<Translations> load(Locale locale) async {
Translations translations = new Translations(locale);
await translations.load();
return translations;
}
#override
bool shouldReload(LocalizationsDelegate<Translations> old) => false;
}
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'components/localization.dart';
import 'components/theme.dart';
import 'views/home.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
onGenerateTitle: (BuildContext context) {
print(context);
return Translations.of(context).get('app_name');
},
theme: appTheme,
home: MyHomePage(
title: "Coba",
),
debugShowCheckedModeBanner: false,
localizationsDelegates: [
TranslationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
supportedLocales: [
Locale("en", ""),
Locale("id", ""),
],
);
}
}
home.dart
import 'package:flutter/material.dart';
import 'package:kpop_idol/components/localization.dart';
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
Translations.of(context).get('app_name'),
),
],
),
),
);
}
}
Don't use relative imports in the file lib/main.dart
import 'components/localization.dart';
import 'components/theme.dart';
import 'views/home.dart';
should be
import 'package:my_package/components/localization.dart';
import 'package:my_package/components/theme.dart';
import 'package:my_package/views/home.dart';
You can upvote and follow https://github.com/dart-lang/sdk/issues/33076
Related
Here is the bloc (simplified):
import 'package:autobleidas_flutter/bloc/bloc_base.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:rxdart/rxdart.dart';
class LoginBloc extends BlocBase {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
final PublishSubject<bool> loggedIn = PublishSubject<bool>();
final PublishSubject<bool> loading = PublishSubject<bool>();
}
Here is the bloc provider:
class BlocProvider<T> extends InheritedWidget {
final T bloc;
BlocProvider({Key key, Widget child, this.bloc})
: super(key: key, child: child);
static T of<T extends BlocBase>(BuildContext context) {
final type = _typeOf<BlocProvider<T>>();
return (context.inheritFromWidgetOfExactType(type) as BlocProvider).bloc;
}
static Type _typeOf<T>() => T;
#override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
}
However, in the LoginScreen I cannot access the loggedIn Subject of the bloc. Here is how LoginScreen is opened from main and the bloc is passed to it:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
localizationsDelegates: GlobalMaterialLocalizations.delegates,
supportedLocales: allTranslations.supportedLocales(),
home: BlocProvider<LoginBloc>(child: LoginScreen()), // <-------- HERE
);
}
}
Here is how I try to access it in the LoginScreen:
class _LoginScreenState extends State<LoginScreen> {
bool _isLoading = false;
#override
void didChangeDependencies() {
LoginBloc bloc = BlocProvider.of<LoginBloc>(context);
bloc.loggedIn.listen((isLoggedIn) => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => RegistrationScreen())));
bloc.loading.listen((state) => setState(() => _isLoading = state));
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
return Container();
}
the error:
The getter 'loggedIn' was called on null.
So why is the bloc null? How do I fix this?
In this line, BlocProvder expect a bloc.
home: BlocProvider<LoginBloc>(child: LoginScreen()),
You are not passing your bloc here.
Pass it like below:
home: BlocProvider<LoginBloc>(child: LoginScreen(),bloc: LoginBloc()),
BlocProvider<LoginBloc> means your defining a type of the bloc you are going to pass.
I am trying to use GoogleTranslator library to translate input text, but i got an error that say type String is not subtype of type Widget
i tried to create a function that receive a text and return the translated text and used the widget on the body of the app
import 'package:flutter/material.dart';
import 'package:translator/translator.dart';
void main() => runApp(MyApp());
Widget translator(String input) {
GoogleTranslator translator = GoogleTranslator();
String translation = translator
.translate("I would buy a car, if I had money.", from: 'en', to: 'ar')
.toString();
return translation as Widget;
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Translator'),
),
body: Center(
child: translator("Hello World"),
),
),
);
}
}
i expect the output to be in translated text in center of the screen
return translation as Widget;
should probably be
return Text(translation);
update
import 'package:flutter/material.dart';
import 'package:translator/translator.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Translator'),
),
body: Center(
child: MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _translations = <String,String>{};
String translator(String input) {
if(_translations.containsKey(input)) {
return _translations[input];
} else {
_translate(input);
return input;
}
}
Future<void> _translate(String input) async {
GoogleTranslator translator = GoogleTranslator();
String translation = await translator
.translate("I would buy a car, if I had money.", from: 'en', to: 'ar');
setState(() => _translations[input] = translation);
}
#override
Widget build(BuildContext context) {
return Text(translator("Hello World"));
}
}
Just testing out flutter. The code sample below is a very simple flutter app. The problem is that I don't know how to call the setState() function inside the TestTextState class in order to change the text each time when the change button is pressed.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test app',
home: new Scaffold(
appBar: new AppBar(
title: new Text("Test"),
),
body: new Test(),
),
);
}
}
class Test extends StatelessWidget {
final TestText testText = new TestText();
void change() {
testText.text == "original" ? testText.set("changed") : testText.set("original");
}
#override
Widget build(BuildContext context) {
return new Column(
children: [
testText,
new RaisedButton(
child: new Text("change"),
onPressed: () => change(),
),
]
);
}
}
class TestText extends StatefulWidget {
String text = "original";
void set(String str) {
this.text = str;
}
#override
TestTextState createState() => new TestTextState();
}
class TestTextState extends State<TestText> {
#override
Widget build(BuildContext context) {
return new Text(this.widget.text);
}
}
I have approached this problem by initializing the _TestTextState as the final property of the TestText widget which allows to simply update the state when the change button is pressed. It seems like a simple solution but I'm not sure whether it's a good practice.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test app',
home: new Scaffold(
appBar: new AppBar(
title: new Text("Test"),
),
body: new Test(),
),
);
}
}
class Test extends StatelessWidget {
final _TestText text = new _TestText();
#override
Widget build(BuildContext context) {
return new Column(
children: [
text,
new RaisedButton(
child: new Text("change"),
onPressed: () => text.update(),
),
]
);
}
}
class TestText extends StatefulWidget {
final _TestTextState state = new _TestTextState();
void update() {
state.change();
}
#override
_TestTextState createState() => state;
}
class _TestTextState extends State<TestText> {
String text = "original";
void change() {
setState(() {
this.text = this.text == "original" ? "changed" : "original";
});
}
#override
Widget build(BuildContext context) {
return new Text(this.text);
}
}
thier is no way to do so. any how you have to convert your StatelessWidget to StatefulWidget.
Solution based on your existing code
class Test extends StatelessWidget {
final StreamController<String> streamController = StreamController<String>.broadcast();
#override
Widget build(BuildContext context) {
final TestText testText = TestText(streamController.stream);
return new Column(children: [
testText,
new RaisedButton(
child: Text("change"),
onPressed: () {
String text = testText.text == "original" ? "changed" : "original";
streamController.add(text);
},
),
]);
}
}
class TestText extends StatefulWidget {
TestText(this.stream);
final Stream<String> stream;
String text = "original";
#override
TestTextState createState() => new TestTextState();
}
class TestTextState extends State<TestText> {
#override
void initState() {
widget.stream.listen((str) {
setState(() {
widget.text = str;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Text(widget.text);
}
}
But it's not the best idea - to use non-final field inside Stateful Widget
P.S.
You can also use this - scoped_model
I want to access _data from main() async to Stateful Widget? Is it good practice to call REST Api Call in Main()?
import 'dart:async';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:flutter/material.dart';
Future main() async {
List _data = await makeRequest();
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
Future<List> makeRequest() async {
String url = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(url);
print(json.decode(response.body));
return json.decode(response.body);
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("JSON List"),
),
body: ListView.builder(
itemBuilder: (BuildContext context, int index) {
ListTile(
);
}
),
);
}
}
This is how it should works, I fixed your code :
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List _data = new List();
void makeRequest() async {
String url = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(url);
print(json.decode(response.body));
setState(() {
_data = json.decode(response.body) as List;
});
}
#override
void initState() {
makeRequest();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("JSON List"),
),
body: _data.isEmpty
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: _data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(_data[index]['title']),
);
}),
);
}
}
your main call should be
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
In my app i've the below pat of the code:
class _MyHomePageState extends State<MyHomePage> {
int _selected = 0;
List<Widget> makeRadios() {
List <Widget> list = new List <Widget>();
list.add(new Row(
children: <Widget>[
new Radio(value: 0, groupValue: _selected,
onChanged: (int value) {
rOnChanged(value);
}),
new Text('Radio 0'),
],
));
list.add(new Row(
children: <Widget>[
new Radio(value: 1, groupValue: _selected,
onChanged: (int value) {
rOnChanged(value);
}),
new Text('Radio 1'),
],
));
return list;
};
void rOnChanged(int value){
this.setState(() {
_selected = value;
});
print("value: $value");
this._bodyHeight = (value == 1) ? 65.0 : 0.0;
}
// and lots more lines
}
I want to split this part and move t to another .dart file in order to reduce the main.dart file, so my two files became like:
library myLib;
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.deepOrange,
),
home: new MyHomePage(title: 'Flutter App Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// other lines
}
And the other file as:
part of myLib;
int _selected = 0;
List<Widget> makeRadios() {
List <Widget> list = new List <Widget>();
list.add(new Row(
children: <Widget>[
new Radio(value: 0, groupValue: _selected,
onChanged: (int value) {
rOnChanged(value);
}),
new Text('Radio 0'),
],
));
list.add(new Row(
children: <Widget>[
new Radio(value: 1, groupValue: _selected,
onChanged: (int value) {
rOnChanged(value);
}),
new Text('Radio 1'),
],
));
return list;
};
void rOnChanged(int value){
this.setState(() {
_selected = value;
});
print("value: $value");
this._bodyHeight = (value == 1) ? 65.0 : 0.0;
}
But it did not work, and the second file became full of errors!
What is the best way to split/scale the Flutter
Don't separate State<T> from it's StatefulWidget.
If you want to extract that widget to it's own file, move both parts entirely.
So you can have
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:myapp/src/myapp.dart';
void main() => runApp(new MyApp());
and in lib/src/myapp.dart
// lib/src/myapp.dart
import 'package:flutter/material.dart';
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new Container();
}
}
After searching and reading the given answers and comments, I think the best way to achieve scalability, is to create separate file for each Widget that include the layout and all related function, then add it for the main file/app, for example I wrote the below code to draw a Switch, read its value and save it in the shared preferences:
file name widget.dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Switchy extends StatefulWidget{
Switchy({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => new _SwitchyState();
}
class _SwitchyState extends State<Switchy> {
var _value = true;
void onchange(bool value) {
setState(() {
_value = value;
_savePref(value);
});
}
_savePref(value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool use = prefs.getBool('use');
await prefs.setBool('use', value);
print('value changed from $use to $value');
}
#override
Widget build(BuildContext context) {
return
new Card(
child: new Container(
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text("Enable/Disable the app in the background"),
new Switch(value: _value, onChanged: (bool value) => onchange(value)),
],
),
),
);
}
}
Then, in the main.dart file I used it as:
import 'widgets.dart';
// ..
children: <Widget>[
new Switchy();
//...
]