Learning Flutter Redux - what is wrong with this code? - redux

I am just getting into Flutter, Dart and Redux. Have followed a YouTube video to modify the default Flutter example to use Redux but its failing for me and I still have a hard time understanding exceptions and reacting to them effectively. Here is the code:
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:redux/redux.dart';
import 'package:flutter_redux/flutter_redux.dart';
// following this youtube video: https://youtu.be/X8B-UzqEaWc
void main() => runApp(new MyApp());
#immutable
class AppState {
final int counter;
AppState(this.counter);
}
// actions
enum Actions { increment }
// pure function
AppState reducer(AppState prev, action) {
if(action == Actions.increment) {
return new AppState(prev.counter + 1);
}
return prev;
}
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.dark(),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
final store = new Store(reducer, initialState: new AppState(0));
//print(store.state.counter); <----- Undefined class 'counter'.
#override
Widget build(BuildContext context) {
return new StoreProvider(
store: store,
child: new Scaffold(
appBar: new AppBar(
title: new Text("Flutter Redux"),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'You have pushed the button this many times:',
),
new StoreConnector(
converter: (store) => store.state.counter,
builder: (context, counter) => new Text(
"$counter",
style: Theme.of(context).textTheme.display1,
)
)
],
),
),
floatingActionButton: new StoreConnector<int, VoidCallback>(
converter: (store) {
return () => store.dispatch(Actions.increment);
},
builder: (context, callback) => new FloatingActionButton(
onPressed: callback,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
)
),
);
}
}
So first of all, when I try to run this, I am getting an exception that states that "The following NoSuchMethodError was thrown building StoreConnector(dirty):
I/flutter (20662): The getter 'store' was called on null.". Secondary question is why the print method highlighted in the code is not recognizing counter getter? Thanks.

The problem was that I had "dart.previewDart2" set to true and I guess something might be screwed up in the latest preview build. Once I set the option to false, all worked good.
Details:
Got to File -> Preferences -> Settings.
Type 'dart" in the search box.
Once you find the setting dart.previewDart2, click on the pencil icon to the left of it and select "Copy to Settings".
On the right hand side in user settings, set the setting to true.

Related

Can't write to Firebase Real Time Database

So, i've setup up a project on Firebase and followed many tutorials on how to connect firebase to my flutter application. I started by reading FlutterFire docs, step by step, and i managed to install the FlutterFire CLI. After that, i went straight into reading the Realtime Database section and whenever i try to write into the db, nothing happens.
The database is in test mode, firebase is correctly initialised inside the project and i get no errors while trying to write inside it, so i think that even the URL is correct (but i might be wrong)
Here's the code i used:
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'firebase_options.dart'; //generated with FlutterFire CLI
import 'package:firebase_core/firebase_core.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
await Firebase.initializeApp(
options: Platform.isWindows ? null : DefaultFirebaseOptions.currentPlatform, //not working on windows
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
final FirebaseDatabase database = FirebaseDatabase.instance;
final DatabaseReference ref = FirebaseDatabase.instance.ref("/");
void writeToFirebase() async {
print('wrote to database');
await ref.set({
'title': 'Hello World',
'body': 'This is my first post',
'userId': '123',
});
await ref.child('title').set('Hello World');
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: writeToFirebase,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}
I noticed that when the print() statement is above ref.set() , i see the output in the console. But, if i put it beneath the ref.set() statement, i see no output. So, this makes me think that the ref.set() does never end its execution or something like that.
I'm 2 weeks in learning flutter and i really can't make Firebase work
Since you're using await, did you consider using try-catch too so that you can catch and log any errors?
So:
void writeToFirebase() async {
print('Start writing to database');
try {
await ref.set({
'title': 'Hello World',
'body': 'This is my first post',
'userId': '123',
});
await ref.child('title').set('Hello World');
}
catch (error) {
print(error);
}
finally {
print('Done writing to database');
}
}

How can I update a Network Image in Flutter

I'm currently learning Flutter and I wanted to try out Network Requests and working with Futures.
I want to show a random image from unsplash.com using their API and I want to change the image every time I press a certain button.
I tried implementing a function to change the image, but it doesn't work.
My code looks like this:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: RandomImage(),
);
}
}
class RandomImage extends StatefulWidget {
#override
_RandomImageState createState() => _RandomImageState();
}
class _RandomImageState extends State<RandomImage> {
static String imageUrl = 'https://source.unsplash.com/random/300x200';
Future _imgFuture = http.get(imageUrl);
void _changeImage() async {
_imgFuture = http.put(imageUrl);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: (Text('Hello')),
),
body: Center(
child: Column(
children: [
Spacer(),
FutureBuilder(
future: _imgFuture,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Oops, there was an error');
} else if (snapshot.hasData) {
return Image.network(imageUrl);
} else {
return Text('No value yet');
}
},
),
RaisedButton(
child: Text('Change Image!'),
onPressed: () => setState(() {
_changeImage();
}),
),
Spacer(),
],
),
),
);
}
}
Actually, Image.network is keeping your image, for more detail to see It here. The solution for this issue is make a simple useless query to api, so the image will be identical differently in flutter.
RaisedButton(
child: Text('Change Image!'),
onPressed: () => setState(() {
// _changeImage();
imageUrl="https://source.unsplash.com/random/300x200?v=${DateTime.now().millisecondsSinceEpoch}";
}),
),
The image won't change If you call the api too frequently, you might want to add a timer to prevent user from clicking too often.
I think the problem is in the _changeImage() method, try replace http.put with http.get.

How to share provider data from streambuilder via different pages (contextes)

I want to have data from firebase in realtime on a widget. When I try to use a StreamProvider and then use Navigator.push(), the pushed widget can't get the value with Provider.of(context).
I tried putting the StreamProvider as the parent of MaterialApp. This works but the user needs to be logged in order for the Stream to get the data of the user.
I also tried using a ScopedModel. This works as well, but I don't know if this is the best approach to do this.
I would like to avoid using a global StreamProvider and would like to have an efficient solution (as little reads from firebase as possible)
main.dart
void main() => runApp(MyApp());
final GlobalKey<ScaffoldState> mainScaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<ScaffoldState> authScaffoldKey = GlobalKey<ScaffoldState>();
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<ScreenModel>(
model: ScreenModel(),
child: MultiProvider(
providers: [
StreamProvider<User>.value(value: authService.userDoc,),
StreamProvider<bool>.value(value: authService.loading.asBroadcastStream())
],
child: MaterialApp(
title: "ListAssist",
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: MainApp()
),
)
);
}
}
class MainApp extends StatefulWidget {
#override
_MainAppState createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
#override
Widget build(BuildContext context) {
User user = Provider.of<User>(context);
bool loading = Provider.of<bool>(context);
return AnimatedSwitcher(
duration: Duration(milliseconds: 600),
child: user != null ?
StreamProvider<Group>.value(
value: databaseService.streamGroupsFromUser(),
child: Scaffold(
key: mainScaffoldKey,
body: Body(),
drawer: Sidebar(),
),
) : Scaffold(
key: authScaffoldKey,
body: AnimatedSwitcher(
duration: Duration(milliseconds: 600),
child: loading ? SpinKitDoubleBounce(color: Colors.blueAccent) : AuthenticationPage(),
),
resizeToAvoidBottomInset: false,
)
);
}
}
class Body extends StatefulWidget {
createState() => _Body();
}
class _Body extends State<Body> {
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<ScreenModel>(
builder: (context, child, model) => model.screen
);
}
}
In the Sidebar I can change to GroupView and the Provider still works.
sidebar.dart (important part)
onTap: () {
ScreenModel.of(context).setScreen(GroupView(), "Gruppen");
Navigator.pop(context);
},
The GroupView has GroupItem in it
group-item.dart (important part)
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return GroupDetail();
}),
)
When I try to use Group group = Provider.of<Group>(context); in GroupDetail or a child widget of it, it says that it cannot find any Provider for the context.
Here is the repository.
I figured out how to do it. I used a package called custom_navigator.
In sidebar.dart I changed the child when someone changes to the group view to the following:
StreamProvider<Group>.value(
value: databaseService.streamGroupsFromUser(user.uid),
child: CustomNavigator(
home: GroupView(),
pageRoute: PageRoutes.materialPageRoute,
)
)
With the CustomNavigator I can still use Provider.of<Group>(context) to get the data, even after a Navigator.push().

How To Make Streams in flutter dart work with provider

This was working initially and it just stopped and I cannot figure out where the problem lies.
I am working with streams on my flutter project in provider package. Streams are being emitted from services files and listening is happening on the widgets file. Firebase onAuthStateChanged stream is working but mine are not working.
I have alot of code in my files so am not going to post everything here.
I have a problem with AuthStatus stream
I tried subscribing to the stream on the widget class but it seems like no streams are getting emitted
MyApp(){
auth.authStateStream.listen((d){print("$d is data");});
}
This how firebase streams are getting emiited from services file
Stream<UserModel> get onAuthStateChanged{
return _firebaseAuth.onAuthStateChanged.map(_userFromFirebase);
}
I have a problem with AuthStatus stream. This was working initially
This is how AuthStatus stream is getting emmited from services file
//Services file
final StreamController<AuthStatus> _currentAuthStateController =
StreamController<AuthStatus>.broadcast();
Stream<AuthStatus> get authStateStream{
return _currentAuthStateController.stream;
}
void testStremas() {
//Stoast.setMessage("Test Message");
_currentAuthStateController.add(AuthStatus.ACTIVE);
}
This is how provider is litening to streams as a parent of the MaterialAPP widget
class MyApp extends StatelessWidget {
//I was trying if i my widget could subscribe to the stream
MyApp (){
auth.authStateStream.listen((d){print("$d is data");});
}
final ToastHelper toast = ToastHelper();
final ThemeHelper theme = ThemeHelper();
final AuthService auth = AuthService();
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<UserModel>.value(value: auth.onAuthStateChanged),
StreamProvider<ToastMessage>.value(value: toast.onNewMessage),
StreamProvider<AuthStatus>.value(
value: auth.authStateStream, initialData: AuthStatus.NONE),
],
child: MaterialApp(
title: Strings.appName,
theme: theme.darkThemeData(),
home: Loader(),
routes: {
'home': (context) => Home(),
},
debugShowCheckedModeBanner: false,
),
);
}
}
This is how the above method is getting called on a the widget on a click of a button
//Widgets file
onTap: () => auth.testStremas(),
The expected result should be when the AuthStatus change from the services file, The widgets should be notified via the provider package. Thanks in advance
Widget _body(BuildContext context) {
final AuthStatus _authStatus = Provider.of<AuthStatus>(context);
return Center(
child: Container(
constraints: BoxConstraints(maxWidth: 300),
child: SingleChildScrollView(
child: Center(
child: _authStatus == AuthStatus.ACTIVE
? Padding(
padding: const EdgeInsets.all(8.0),
child:CircularProgressIndicator(strokeWidth: 2,)
)
: _buildScreen(context),
)),
),
);
}
I'm not sure - try to change
final StreamController<AuthStatus> _currentAuthStateController =
StreamController<AuthStatus>.broadcast();
to
final StreamController<AuthStatus> _currentAuthStateController =
BehaviorSubject<AuthStatus>();
This BehaviorSubject from rxdart library https://pub.dev/packages/rxdart, so, you should import it. BehaviorSubject is keep last state of stream. You can read more here https://pub.dev/documentation/rxdart/latest/rx/BehaviorSubject-class.html
import 'package:rxdart/rxdart.dart';

type 'List<dynamic>' is not a subtype of type 'List<Widget>'

I have a snippet of code which I copied from Firestore example:
Widget _buildBody(BuildContext context) {
return new StreamBuilder(
stream: _getEventStream(),
builder: (context, snapshot) {
if (!snapshot.hasData) return new Text('Loading...');
return new ListView(
children: snapshot.data.documents.map((document) {
return new ListTile(
title: new Text(document['name']),
subtitle: new Text("Class"),
);
}).toList(),
);
},
);
}
But I get this error
type 'List<dynamic>' is not a subtype of type 'List<Widget>'
What goes wrong here?
The problem here is that type inference fails in an unexpected way. The solution is to provide a type argument to the map method.
snapshot.data.documents.map<Widget>((document) {
return new ListTile(
title: new Text(document['name']),
subtitle: new Text("Class"),
);
}).toList()
The more complicated answer is that while the type of children is List<Widget>, that information doesn't flow back towards the map invocation. This might be because map is followed by toList and because there is no way to type annotate the return of a closure.
I had a list of strings in firestore that I was trying to read in my app. I got the same error when I tried to cast it to List of String.
type 'List<dynamic>' is not a subtype of type 'List<Widget>'
This solution helped me. Check it out.
var array = document['array']; // array is now List<dynamic>
List<String> strings = List<String>.from(array);
You can Cast dynamic List to List With specific Type:
List<'YourModel'>.from(_list.where((i) => i.flag == true));
I have solve my problem by converting Map to Widget
children: snapshot.map<Widget>((data) =>
_buildListItem(context, data)).toList(),
I think that you use _buildBody in the children properties of some widget, so children expect a List Widget (array of Widget) and _buildBody returns a 'List dynamic'.
In a very simple way, you can use an variable to return it:
// you can build your List of Widget's like you need
List<Widget> widgets = [
Text('Line 1'),
Text('Line 2'),
Text('Line 3'),
];
// you can use it like this
Column(
children: widgets
)
Example (flutter create test1 ; cd test1 ; edit lib/main.dart ; flutter run):
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Widget> widgets = [
Text('Line 1'),
Text('Line 2'),
Text('Line 3'),
];
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("List of Widgets Example")),
body: Column(
children: widgets
)
)
);
}
}
Another example using a Widget (oneWidget) within a List of Widgets(arrayOfWidgets). I show how extents a widget (MyButton) to personalize a widget and reduce the size of code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Widget> arrayOfWidgets = [
Text('My Buttons'),
MyButton('Button 1'),
MyButton('Button 2'),
MyButton('Button 3'),
];
Widget oneWidget(List<Widget> _lw) { return Column(children: _lw); }
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Widget with a List of Widget's Example")),
body: oneWidget(arrayOfWidgets)
)
);
}
}
class MyButton extends StatelessWidget {
final String text;
MyButton(this.text);
#override
Widget build(BuildContext context) {
return FlatButton(
color: Colors.red,
child: Text(text),
onPressed: (){print("Pressed button '$text'.");},
);
}
}
I made a complete example that I use dynamic widgets to show and hide widgets on screen, you can see it running online on dart fiddle, too.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List item = [
{"title": "Button One", "color": 50},
{"title": "Button Two", "color": 100},
{"title": "Button Three", "color": 200},
{"title": "No show", "color": 0, "hide": '1'},
];
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Dynamic Widget - List<Widget>"),backgroundColor: Colors.blue),
body: Column(
children: <Widget>[
Center(child: buttonBar()),
Text('Click the buttons to hide it'),
]
)
)
);
}
Widget buttonBar() {
return Column(
children: item.where((e) => e['hide'] != '1').map<Widget>((document) {
return new FlatButton(
child: new Text(document['title']),
color: Color.fromARGB(document['color'], 0, 100, 0),
onPressed: () {
setState(() {
print("click on ${document['title']} lets hide it");
final tile = item.firstWhere((e) => e['title'] == document['title']);
tile['hide'] = '1';
});
},
);
}
).toList());
}
}
Maybe it helps someone. If it was is useful to you, let me know clicking in up arrow, please. Thanks.
https://dartpad.dev/b37b08cc25e0ccdba680090e9ef4b3c1
My Solution is, you can cast List<dynamic> into List<Widget>, you can add a simple code after snapshot.data.documents.map into snapshot.data.documents.map<Widget>, like the code i will show you below
From this
return new ListView(
children: snapshot.data.documents.map((document) {
return new ListTile(
title: new Text(document['name']),
subtitle: new Text("Class"),
);
}).toList(),
);
Into this
return new ListView(
children: snapshot.data.documents.map<Widget>((document) {
return new ListTile(
title: new Text(document['name']),
subtitle: new Text("Class"),
);
}).toList(),
);
This works for meList<'YourModel'>.from(_list.where((i) => i.flag == true));
Example:
List<dynamic> listOne = ['111','222']
List<String> ListTwo = listOne.cast<String>();
I think , after changing the type of List from dynamic to String & doing hot reload will give this error , hot restart is the solution..
Remember : hot restart only rebuild the build() function , not the whole class & declaring the List at the top of class is outside of build() function
changing to the list by adding .toList() resolved the issue
Changing
List list = [];
to this:
List<Widget> list = [];
Solved My Problem!!

Resources