How to properly initialize variables with Localization? - flutter

I've followed a lot of tutorials and optimized the localization as much as possible.
The problem here is that the variables aren't properly initialized. First instance it shows errors of variables not being initialized. I then do a hot reload without changing anything. Now it rebuilds and then I navigated to the settings page to change the language and it did updated the strings.
So my main issue here is why are my variables not being initialized at first instance?
main.dart:
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) => ChangeNotifierProvider(
create: (context) => LocaleProvider(),
builder: (context, child) {
final provider = Provider.of<LocaleProvider>(context).locale;
return ThemeProvider(
saveThemesOnChange: true,
loadThemeOnInit: true,
child: LayoutBuilder(builder: (context, constraints) {
return OrientationBuilder(builder: (context, orientation) {
SizeConfig().init(constraints, orientation);
return MaterialApp(
theme: CustomTheme.lightTheme,
debugShowCheckedModeBanner: false,
routes: <String, WidgetBuilder>{
'/splash': (BuildContext context) =>
const SplashPortrait(),
},
locale: provider,
localizationsDelegates:
AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: ThemeConsumer(
child: orientation == Orientation.portrait
? const SplashPortrait()
: const SplashLandscape(),
),
);
});
}));
},
);
}
class SplashPortrait extends StatefulWidget {
const SplashPortrait({Key? key}) : super(key: key);
#override
State<SplashPortrait> createState() => _SplashPortraitState();
}
class _SplashPortraitState extends State<SplashPortrait>
with TickerProviderStateMixin {
late SharedPreferences sharedPreferences;
#override
void initState() {
super.initState();
init();
}
Future init() async {
sharedPreferences = await SharedPreferences.getInstance();
final rememberMe = sharedPreferences.getBool("rememberMe");
if (rememberMe != null) {
if (rememberMe) {
final profile = sharedPreferences.getString("currentUser");
if (profile == null) return;
Constants.currentProfile = Profile.fromJson(json.decode(profile));
Constants.token = sharedPreferences.getString("token")!;
Constants.headers = {
"Content-Type": 'application/json',
"x-access-token": Constants.token,
};
Future.delayed(const Duration(milliseconds: 4000), () {
Navigator.of(context)
.push(SlideRightRoute(page: const HomeScreen(fragmentIndex: 0)));
});
} else {
Future.delayed(const Duration(milliseconds: 4000), () {
Navigator.of(context)
.push(SlideRightRoute(page: const LoginScreen()));
});
}
} else {
Future.delayed(const Duration(milliseconds: 4000), () {
Navigator.of(context).push(SlideRightRoute(page: const LoginScreen()));
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Center(
child: SizedBox(
width: MediaQuery.of(context).size.width / 2,
child: const RiveAnimation.asset(
'assets/splash.riv',
),
),
),
);
}
}
Login Page is where I am experiencing errors:
class LoginScreen extends StatefulWidget {
const LoginScreen({
Key? key,
}) : super(key: key);
#override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
late LoginBloc loginBloc;
FocusNode emailFocus = FocusNode();
FocusNode passwordFocus = FocusNode();
late TextEditingController emailController;
late TextEditingController passwordController;
late bool isPasswordVisible;
late String emailHint;
late String passwordHint;
late bool rememberMe;
#override
void initState() {
super.initState();
init();
}
init() {
emailController = TextEditingController();
passwordController = TextEditingController();
emailHint = context.loc.emailPlaceholder;
passwordHint = context.loc.passwordPlaceholder;
emailFocus.addListener(onEmailFocusChanged);
passwordFocus.addListener(onPasswordFocusChanged);
isPasswordVisible = false;
rememberMe = false;
loginBloc = LoginBloc(context: context, rememberMe: false);
}
void onEmailFocusChanged() {
if (emailFocus.hasFocus) {
setState(() {
emailHint = "";
});
} else {
setState(() {
emailHint = context.loc.emailPlaceholder;
});
}
}
void onPasswordFocusChanged() {
if (passwordFocus.hasFocus) {
setState(() {
passwordHint = "";
});
} else {
setState(() {
passwordHint = context.loc.passwordPlaceholder;
});
}
}
#override
void dispose() {
loginBloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: colorWhite,
body: SafeArea(
child: WillPopScope(
child: LoginPortrait(context),
onWillPop: () => onWillPop(),
)),
);
}
onWillPop() {
SystemNavigator.pop();
}
context.loc.
extension LocalizedBuildContext on BuildContext {
AppLocalizations get loc => AppLocalizations.of(this);
}
Solutions I've tried:
1.) Future.delayed
2.) WidgetsBinding.instance?.addPostFrameCallback
3.) SchedulerBinding.instance!.addPostFrameCallback
4.) didChangeDependencies()

Related

How to send the fetched data everytime to some other widget in Flutter

Wanted to pass the updated values of fetchedEntriesInApp to PasswdList widget everytime it loads.
Below is my code.
main.dart
Future fetchEntries() async {
var fetchedEntries = [];
var db = FirebaseFirestore.instance;
final res = await db.collection("password_entries").get().then((event) {
for (var doc in event.docs) {
var resDic = {
"entry_id": doc.id,
"data": doc.data(),
};
fetchedEntries.add(resDic);
}
});
return fetchedEntries;
}
class Body extends StatefulWidget {
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
late Future fetchedEntriesInApp;
#override
void initState() {
super.initState();
fetchedEntriesInApp = fetchEntries();
}
void refreshEntries() {
setState(() {
fetchedEntriesInApp = fetchEntries();
});
}
#override
Widget build(BuildContext context) {
setState(() {});
return FutureBuilder(
future: fetchedEntriesInApp!,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('Loading');
}
return Column(children: [
PasswdList(fetchedEntriesInApp),
RaisedButton(
onPressed: () {
Navigator.pushNamed(
context,
'/addPasswd',
arguments: AddPasswd(fetchEntries),
);
},
child: Text('Add Psswd'),
),
]);
});
}
}
PasswdList Widget
class PasswdList extends StatefulWidget {
var abc;
PasswdList(this.abc);
#override
State<PasswdList> createState() => _PasswdListState();
}
class _PasswdListState extends State<PasswdList> {
var fetchedEntriesInApp;
#override
Widget build(BuildContext context) {
var entries;
setState(() {
entries = widget.abc;
});
print(entries);
return Container(
height: 500,
child: ListView(
children: [
PasswdCard(),
],
),
);
}
}
You can add one variable for password list in your password list widget like,
class PasswdList extends StatefulWidget {
var passwordlist;
PasswdList(this.passwordlist);
#override
State<PasswdList> createState() => _PasswdListState();
}
class _PasswdListState extends State<PasswdList> {
var fetchedEntriesInApp;
#override
Widget build(BuildContext context) {
var entries;
setState(() {
entries = widget.passwordlist;
});
print(entries);
return Container(
height: 500,
child: ListView(
children: [
PasswdCard(),
],
),
);
}
}
And you can pass it to the navigator like,
RaisedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>PasswdList (fetchedEntriesInApp.values,
),
);
},
Since your PasswdList is a Stateful widget and it is embedded in your view, you can use the callback
#override
void didUpdateWidget(covariant PasswdList oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.abc != oldWidget.abc)
setState(() {
//You can have a var in your state class and re-assign it to the new value
});
}
Note: in order for this to work, you need to re-initialize the abc list and pass it to your widget, otherwise you might need to change the if statement condition

How to make StreamBuilder not rebuild all widget in flutter

I am currently working on a chat application, when I added a record everything is fine but when play record or any audio and send message audio is stop and make initState again
this is streambuilder:
class MessageStreamBuilder extends StatefulWidget {
final ScrollController messageScrollController;
const MessageStreamBuilder({Key? key, required this.messageScrollController}) : super(key: key);
#override
State<MessageStreamBuilder> createState() => _MessageStreamBuilderState();
}
class _MessageStreamBuilderState extends State<MessageStreamBuilder> {
late Stream<QuerySnapshot<Map<String, dynamic>>> _stream;
late ChatCubit cubit;
final int _limit = 100;
final int _limitIncrement = 20;
#override
void initState() {
//_messageScrollController.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.easeOut);
super.initState();
_stream = FirebaseFirestore.instance
.collection('GeneralChat')
.orderBy('time', descending: true)
.limit(_limit)
.snapshots();
cubit = ChatCubit.get(context);
}
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<QueryDocumentSnapshot<Object?>> message = snapshot.data!.docs;
return ListView.builder(
controller: widget.messageScrollController,
reverse: true,
itemBuilder: (element, index) {
return Message(
messages: message, messageIndex: index);
},
);
} else {
return const Center(
child: SpinKitWave(
color: Colors.white,
size: 50.0,
),
);
}
});
}
}
and this is audio widget:
class _AudioMessageState extends State<AudioMessage> {
final AudioPlayer _player = AudioPlayer();
final url = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3';
#override
void initState() {
super.initState();
_init();
}
Future<void> _init() async {
try {
await _player.setUrl(widget.audioUrl[0]);
} catch (e) {
debugPrint('An error occured $e');
}
}
#override
void dispose() {
_player.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Row(
children: [
playButtonAudio(_player),
const SizedBox(width: 10,),
Expanded(
child: progressBarAudio(_player)),
],
);
}
}
i need to make audio play when send message dont stop Is there another way instead of StreamBuilder....
iam using firebase
and no setState() use cubit

Show a dialog instead of return a widget using Either in Flutter/Dart

I'm a little stuck and can't figure out how the architectural flow should work in this use case. I know almost nothing about functional programming, I'm using this Either from dartz package, a functional programming package. Can someone help me with the following:
I want to show a popup dialog instead of a widget if there is an error. But the design of Either seems to not allow this somehow as this if logic requires a widget of course. Is there a better design which I could accomplish this with?
Learning error handling here
import 'dart:convert';
import 'dart:io';
import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:awesome_dialog/awesome_dialog.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.orange,
),
home: const HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueGrey,
body: Flex(
direction: Axis.horizontal,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Consumer(
builder: (ctx, ref, child) {
if (ref.watch(notifier).state == NotifierState.initial) {
return const Text('Press the button');
} else if (ref.watch(notifier).state == NotifierState.loading) {
return const CircularProgressIndicator();
} else {
return ref.read(notifier).post.fold(
(failure) {
showDialog( /// Error here, expects a widget
context: context,
barrierDismissible: true,
builder: (BuildContext context) => AlertDialog(
title: Text(failure.toString()),
),
);
},
(post) => Text(post.toString()),
);
}
},
),
Consumer(
builder: (ctx, ref, child) {
return ElevatedButton(
onPressed: () {
ref.read(notifier).getOnePost();
},
child: const Text('Get Post'));
},
),
],
),
),
],
),
);
}
}
class PostService {
final httpClient = FakeHttpClient();
Future<Post?> getOnePost() async {
try {
final responseBody = await httpClient.getResponseBody();
return Post.fromJson(responseBody);
} on SocketException {
throw Failure('No Internet connection 😑');
} on HttpException {
throw Failure("Couldn't find the post 😱");
} on FormatException {
throw Failure("Bad response format 👎");
}
}
}
class FakeHttpClient {
Future<String> getResponseBody() async {
await Future.delayed(const Duration(milliseconds: 500));
//! No Internet Connection
// throw SocketException('No Internet');
//! 404
throw HttpException('404');
//! Invalid JSON (throws FormatException)
// return 'abcd';
// return '{"userId":1,"id":1,"title":"nice title","body":"cool body"}';
}
}
enum NotifierState { initial, loading, loaded }
final notifier = ChangeNotifierProvider((ref) => PostChangeNotifier());
class PostChangeNotifier extends ChangeNotifier {
final _postService = PostService();
NotifierState _state = NotifierState.initial;
NotifierState get state => _state;
void _setState(NotifierState state) {
_state = state;
notifyListeners();
}
late Either<Failure, Post?> _post;
Either<Failure, Post?> get post => _post;
// Set post
void _setPost(Either<Failure, Post?> post) {
_post = post;
notifyListeners();
}
// Set one post
void getOnePost() async {
_setState(NotifierState.loading);
await Task(() => _postService.getOnePost())
.attempt()
.mapLeftToFailure()
.run()
.then((value) => _setPost(value as Either<Failure, Post?>));
_setState(NotifierState.loaded);
}
}
extension TaskX<T extends Either<Object, U>, U> on Task<T> {
Task<Either<Failure, U>> mapLeftToFailure() {
return map(
(either) => either.leftMap((obj) {
try {
return obj as Failure;
} catch (e) {
throw obj;
}
}),
);
}
}
class Post {
final int id;
final int userId;
final String title;
final String body;
Post({
required this.id,
required this.userId,
required this.title,
required this.body,
});
static Post? fromMap(Map<String, dynamic> map) {
return Post(
id: map['id'],
userId: map['userId'],
title: map['title'],
body: map['body'],
);
}
static Post? fromJson(String source) => fromMap(json.decode(source));
#override
String toString() {
return 'Post id: $id, userId: $userId, title: $title, body: $body';
}
}
class Failure {
// Use something like "int code;" if you want to translate error messages
final String message;
Failure(this.message);
#override
String toString() => message;
}
you don't call a function instead of widget, You should call class and initialize your dialog in initState
// call show dialog
(failure) {
ShowDialogScreen(failure: failure.toString());
},
// show dialog screen
class ShowDialogScreen extends StatefulWidget {
final String failure;
const ShowDialogScreen({Key key, this.failure}) : super(key: key);
#override
_ShowDialogScreenState createState() => _ShowDialogScreenState();
}
class _ShowDialogScreenState extends State<ShowDialogScreen> {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) => AlertDialog(
title: Text(widget.failure),
),
);
});
}
#override
Widget build(BuildContext context) {
return Container();
}
}

Trigger floatingActionButton onPressed without pressing the button

I'm new to Flutter and I'm making a To Do app. I want to be able to open a showModalBottomSheet widget when I click an ElevatedButton that belongs to another widget. Ideally it would open when the user clicks "Edit" belonging to one of the ToDo widgets.
Worst case I could probably use another showModalBottomSheet for the edit action but I'd love to be able to reuse my existing showModalBottomSheet for edits as well as new to do's since it's already in place. All I need to do is trigger it to reopen when the user selects "Edit".
Here is my code in MyApp. I can include code for NewToDo if requested but I feel like that code isn't the issue.
import 'package:flutter/material.dart';
import './todoitem.dart';
import './todolist.dart';
import 'classes/todo.dart';
import './newtodo.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'To Do Homie',
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
home: const MyHomePage(title: "It's To Do's My Guy"),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String content = '';
String title = '';
int maxId = 0;
ToDo? _todo;
final titleController = TextEditingController();
final contentController = TextEditingController();
List<ToDo> _todos = [];
void _addTodo(){
final todo = ToDo (
title: title,
id: maxId,
isDone: false,
content: content
);
if (_todo != null){
setState(() {
_todos[_todos.indexOf(_todo!)] = todo;
});
} else {
setState(() {
_todos.add(todo);
});
}
setState(() {
content = '';
maxId = maxId++;
title = '';
_todo = null;
});
contentController.text = '';
titleController.text = '';
}
#override
void initState() {
super.initState();
titleController.addListener(_handleTitleChange);
contentController.addListener(_handleContentChange);
}
void _handleTitleChange() {
setState(() {
title = titleController.text;
});
}
void _handleContentChange() {
setState(() {
content = contentController.text;
});
}
void _editTodo(ToDo todoitem){
setState(() {
_todo = todoitem;
content = todoitem.content;
title = todoitem.title;
});
contentController.text = todoitem.content;
titleController.text = todoitem.title;
}
void _deleteToDo(ToDo todoitem){
setState(() {
_todos = List.from(_todos)..removeAt(_todos.indexOf(todoitem));
});
}
void _clear(){
contentController.text = '';
titleController.text = '';
setState(() {
content = '';
title = '';
_todo = null;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Center(
child: Container(
alignment: Alignment.topCenter,
child: ToDoList(_todos, _editTodo, _deleteToDo)
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return ValueListenableBuilder(
valueListenable: titleController,
builder: (context, _content, child) {
return NewToDo(titleController, contentController, _addTodo, _clear, _todo);
});
});
},
child: const Icon(Icons.add),
backgroundColor: Colors.deepPurple,
),
);
}
}
Try extracting the showModalBottomSheet in its own method and pass it to the onPressed like this:
floatingActionButton: FloatingActionButton(
onPressed: _triggerBottomSheet,
// then create the method
void _triggerBottomSheet(){
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return ValueListenableBuilder(
valueListenable: titleController,
builder: (context, _content, child) {
return NewToDo(titleController, contentController, _addTodo, _clear, _todo);
});
});

Bloc navigation on state change

I'm really new with flutter blocs and I having some problems with a bloc implementation, I'm trying to navigate after a state change in my splash screen widget.
After the state update to InitSuccess it should navigate to LoginScreen, but this navigation occurs many times.
I'm not able to understand what to do after the state change's to InitSuccess, after this the bloc keeps alive and calling many, many times LoginScreen.
Splash Screen
class SplashScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
SplashBloc _splashBloc;
final _scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
_init();
super.initState();
}
#override
void dispose() {
_splashBloc.dispose();
super.dispose();
}
void _init() {
Future.delayed(Duration.zero, () {
checkDeviceConnection(context);
BlocSupervisor().delegate = SplashBlocDelegate();
final bool isIOS = Theme.of(context).platform == TargetPlatform.iOS;
_splashBloc = SplashBloc(
firebaseService: FirebaseService(context),
authService: AuthService(context),
devicesService: DevicesService(context),
);
_splashBloc.dispatch(SplashInitEvent(isIOS: isIOS));
});
#override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return BlocBuilder<SplashEvent, SplashState>(
bloc: _splashBloc,
builder: (
BuildContext context,
SplashState state,
) {
if (state is InitFailure) {
Future.delayed(Duration.zero, () {
showWarningSnackBar(_scaffoldKey, state.error);
});
}
if (state is InitSuccess) {
Future.delayed(Duration.zero, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginScreen(),
),
);
});
}
return Scaffold(
key: _scaffoldKey,
body: Container(
decoration: appScreenGradient,
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(
"assets/images/splash_screen/logo_splash.png",
width: 172.88,
height: 144.55,
fit: BoxFit.contain,
),
SizedBox(
height: 20.0,
),
LoadingSpinner(
spinnerColor: Theme.of(context).primaryColorLight,
),
],
),
),
);
},
);
}
Splash Bloc
class SplashBloc extends Bloc<SplashEvent, SplashState> {
final FirebaseService firebaseService;
final DevicesService devicesService;
final AuthService authService;
final UserPreferences _userPreferences = UserPreferences();
SplashBloc({
#required this.firebaseService,
#required this.devicesService,
#required this.authService,
});
#override
Stream<SplashEvent> transform(Stream<SplashEvent> events) {
return (events as Observable<SplashEvent>).debounce(
Duration(milliseconds: 500));
}
#override
get initialState => SplashInitial();
#override
Stream<SplashState> mapEventToState(currentState, event) async* {
if (event is SplashInitEvent) {
if (currentState is SplashInitial) {
yield InitLoading();
try {
firebaseService.togglePerformanceCollection(true);
firebaseService.firebaseCloudMessagingListeners();
String firebaseToken = await firebaseService
.getFirebaseMessagingToken();
bool isRegistered =
await _userPreferences.getIsDeviceRegistered() ?? false;
if (!isRegistered) {
final String platform = event.isIOS ? 'IOS' : 'Android';
final deviceInfo = await devicesService.getDeviceInfo(platform);
isRegistered = await devicesService.register(
deviceToken: firebaseToken,
deviceInfo: deviceInfo,
);
if (isRegistered) {
_userPreferences.setIsDeviceRegistered(true);
}
}
yield InitSuccess();
} catch (e) {
yield InitFailure(error: e.toString());
}
}
}
if (event is SplashInitialEvent) {
yield SplashInitial();
}
}
}
I found the following solution:
if (state is LoggedIn) {
WidgetsBinding.instance.addPostFrameCallback((_) {
// Navigation
});
}
I wrapped my navigation with this addPostFrame callback for delaying its appearance.