This is one official example of simple BarChart for Flutter app - https://google.github.io/charts/flutter/example/bar_charts/simple
I wood like fetch data from Internet for this chart. Here is the data - http://skazkimal.ru/hr-metrics/headcount.json
My code is not work, because method _createSampleData is async:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:hr_metrics/FetchChartData.dart';
import 'package:http/http.dart' as http;
class SalaryView extends StatelessWidget{
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('FFFFFFFFF'),
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: new SalaryChart.withSampleData(),
),
);
}
}
class SalaryChart extends StatelessWidget{
final List<charts.Series> seriesList;
final bool animate;
SalaryChart(this.seriesList, {this.animate});
/// Creates a [BarChart] with sample data and no transition.
factory SalaryChart.withSampleData() {
return new SalaryChart(
_createSampleData(),
// Disable animations for image tests.
animate: true,
);
}
#override
Widget build(BuildContext context) {
// This is just a simple bar chart with optional property
// [defaultInteractions] set to true to include the default
// interactions/behaviors when building the chart.
// This includes bar highlighting.
//
// Note: defaultInteractions defaults to true.
//
// [defaultInteractions] can be set to false to avoid the default
// interactions.
return new charts.BarChart(
seriesList,
animate: animate,
defaultInteractions: true,
barRendererDecorator: new charts.BarLabelDecorator<String>(),
vertical: false,
);
}
/// Create one series with sample hard coded data.
static Future<List<charts.Series<ChartData, String>>> _createSampleData() async {
final data = await fetchData(http.Client());
return [
new charts.Series<ChartData, String>(
id: 'Numbers',
domainFn: (ChartData series, _) => series.period,
measureFn: (ChartData series, _) => series.count,
data: data,
labelAccessorFn: (ChartData series, _) => '${series.count.toString()}'
)
];
}
}
/// Sample ordinal data type.
class OrdinalSalary {
final String year;
final int salary;
OrdinalSalary(this.year, this.salary);
}
You cannot use an async method in a constructor like that. Instead, you should create an async method like so:
static Future<SalaryChart> withSampleData() => _createSampleData().then((data) => SalaryChart(data, animate: true));
Which you would use elsewhere:
SalaryChart myChart = await SalaryChart.withSampleData();
Related
I am currently building an app to get a data from a json api.
I want to get the number 54 from the json code.
here is the json link
I have tried making model class of the json api here
class TeerModel{
String text;
TeerModel(this.text);
TeerModel.fromJson(Map<String, dynamic>parsedJson){
text = parsedJson['text'];
}
}
But I can't get the result so i removed it
Here is the code
import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
import 'models/teer_model.dart';
import 'dart:convert';
class Appss extends StatefulWidget {
#override
_AppssState createState() => _AppssState();
}
class _AppssState extends State<Appss> {
String result = "1S";
void fetchData ()async{
var response1 = await get("http://motyar.info/webscrapemaster/api/?url=http://teertoday.com/&xpath=/html/body/div[5]/div/table/tbody/tr[3]/td[1]#vws");
var teerModel = json.decode(response1.body);
var line = teerModel["text"].replaceAll(new RegExp(r"(\s\n)"), "");
print(line);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Teer result"),),
floatingActionButton: FloatingActionButton(
onPressed: fetchData,
),
body: Center(
child: Text("The result is: $result"),
),
),
);
}
}
I only want to get the number 54 from "text" so I use regex
I expected the output will be 54 but instead I get this error
If you look at your json, you will see that it is entirely surrounded by [...], meaning that it is a json array. json.decode will convert this into a Dart List<Map<String, dynamic>>. It looks like you want the first / zero'th element of this array/list.
Change:
var line = teerModel["text"].replaceAll(new RegExp(r"(\s\n)"), "");
to
var line = teerModel[0]["text"].replaceAll(new RegExp(r"(\s\n)"), "");
Don't forget to call setState so that your widget rebuilds itself.
i want to be able to call an empty variable from a class, assign a value to it and make it persistent, anything aside provider e.t.c would be help, i don't want to overhaul the entire app again to do some bloc, provider e.t.c
NB: all screens are stateful widgets
i have tried creating a class with an empty string and passing a value to it from another screen, but this doesn't seem to work
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
class MethodA {
// id(user, context){
// var name =user.email;
// }
String identity;
MethodA({this.iD});
bool isLoggedIn() {
if (FirebaseAuth.instance.currentUser() != null) {
return true;
} else {
return false;
}
}
Future<void> addUserA( userinfo) async {
//this.iD=id;
Firestore.instance
.collection('user')
.document('furtherinfo').collection(identity).document('Personal Info')
.setData(userdoc)
.catchError((e) {
print(e);
});
}
each time i pass the argument to i.e foo='bar';
and i import that class in another screen, i.e screen 9, foo is automatically set to null, but i would want foo to be bar
I would suggest that you use the Provider since it is the easiest way for me to manage state throughout the app. Flutter starts with one component on top of the widget tree so i would place my provider here.
Example
void main() {runApp(MyApp());}
class MyApp extends StatelessWidget {
MyApp();
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<FirebaseUser>.value(
stream: FirebaseAuth.instance.onAuthStateChanged, // Provider to manage user throughout the app.
),
],
child: MaterialApp(
title: 'My App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.green,
primarySwatch: Colors.green,
accentColor: Colors.yellow,
),
home: MainPage(),
),
);
}
}
Then in your class you can do the following
class MethodAService with ChangeNotifier {
String _identity = null;
FirebaseUser _user = null;
// constructor with the (new changes )
MethodAService(FirebaseUser user){
this._user = user;
}
get identity => _identity ;
setIdentity(String identity) {
_identity = identity ;
notifyListeners(); // required to notify the widgets of your change
}
}
Then when you want to use it anywhere in your app just do the following in the build method
#override
Widget build(BuildContext context) {
final user = Provider.of<FirebaseUser>(context); // to get the current user
final methodA = Provider.of<MethodAService>(context); // get your service with identity
// now you can set the string using
methodA.setIdentity('new identity');
// or just use it like this
if(methodA.identity.isNotEmpty()){
print(methodA.identity);
}else{
print('Identity is empty');
}
return ChangeNotifierProvider<MethodAService>(
builder: (context) => MethodAService(user), // Your provider to manage your object, sending the Firebase user in
child: loggedIn ? HomePage() : LoginPage(), );
}
References
Provider Package
Fireship 185 Provider
Great Youtube video explaining the code
Update for comment
For getting the user uid you can just do user.uid
Changed code above to fit the
I'm not sure put the whole app in a StreamProvider is the best choice. That means the app will be rebuilt on each stream value.
To make a Widget available on all screens, you need a TransitionBuilder in your MaterialApp.
To avoid the external dependency you can also use an InheritedWidget
signed_user.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class SignedUser extends InheritedWidget {
final FirebaseUser user;
SignedUser({#required this.user, #required Widget child})
: super(child: child);
#override
bool updateShouldNotify(SignedUser oldWidget) => true;
static SignedUser of(BuildContext context) =>
context.inheritFromWidgetOfExactType(SignedUser);
}
my_transition_builder.dart
class MyTransitionBuilder extends StatefulWidget {
final Widget child;
const MyTransitionBuilder({Key key, this.child}) : super(key: key);
#override
_MyTransitionBuilderState createState() => _MyTransitionBuilderState();
}
class _MyTransitionBuilderState extends State<MyTransitionBuilder> {
StreamBuilder<FirebaseUser> _builder;
#override
void initState() {
super.initState();
_builder = StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
return SignedUser(
child: widget.child,
user: snapshot.data,
);
});
}
#override
Widget build(BuildContext context) {
return _builder;
}
}
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
// this will make your inherited widget available on all screens of your app
builder: (context, child) {
return MyTransitionBuilder(child: child);
},
routes: {
'/editAccount': (context) => new EditAccountPage(),
},
theme: ThemeData(
primarySwatch: Colors.green,
),
home: MyHomePage(),
);
}
}
usage in edit_account_page.dart
#override
Widget build(BuildContext context) {
var user = SignedUser.of(context).user;
return Scaffold(
body: FutureBuilder<DocumentSnapshot>(
future: Firestore.instance.document('users/${user.uid}').get(),
I am currently building an app to get a data from a json api.
I want to get the number 54 from the json code.
here is the json link
I have tried making model class of the json api here
class TeerModel{
String text;
TeerModel(this.text);
TeerModel.fromJson(Map<String, dynamic>parsedJson){
text = parsedJson['text'];
}
}
But I can't get the result so i removed it
Here is the code
import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
import 'models/teer_model.dart';
import 'dart:convert';
class Appss extends StatefulWidget {
#override
_AppssState createState() => _AppssState();
}
class _AppssState extends State<Appss> {
String result = "1S";
void fetchData ()async{
var response1 = await get("http://motyar.info/webscrapemaster/api/?url=http://teertoday.com/&xpath=/html/body/div[5]/div/table/tbody/tr[3]/td[1]#vws");
var teerModel = json.decode(response1.body);
var line = teerModel["text"].replaceAll(new RegExp(r"(\s\n)"), "");
print(line);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Teer result"),),
floatingActionButton: FloatingActionButton(
onPressed: fetchData,
),
body: Center(
child: Text("The result is: $result"),
),
),
);
}
}
I only want to get the number 54 from "text" so I use regex
I expected the output will be 54 but instead I get this error
If you look at your json, you will see that it is entirely surrounded by [...], meaning that it is a json array. json.decode will convert this into a Dart List<Map<String, dynamic>>. It looks like you want the first / zero'th element of this array/list.
Change:
var line = teerModel["text"].replaceAll(new RegExp(r"(\s\n)"), "");
to
var line = teerModel[0]["text"].replaceAll(new RegExp(r"(\s\n)"), "");
Don't forget to call setState so that your widget rebuilds itself.
I´m trying to navigate between two pages. In second page I need a constructor so i need to declare the variable null in first page and initialize it in the second page.
In main.dart (first page) i have similar to
PostDetailsPage.tag: (context) => PostDetailsPage(new List()),
In the second page (PostDetailsPage) i have this
final List<charts.Series> seriesList;
final bool animate;
PostDetailsPage(this.seriesList, {this.animate});
When i go to page two return
< List < Series< dynamic, dynamic > > is not a subtype of type < List < Series< dynamic, String>>
So how can i solve? Initializate the value in first page and pass it? Initializate value as null in first page and pass it?
UPDATED
Main.dart class
import 'package:flutter/material.dart';
import 'package:fluttercrud/screens/login_page.dart';
import 'package:fluttercrud/screens/home_page.dart';
import 'package:fluttercrud/screens/partials/list_post.dart';
import 'package:fluttercrud/screens/maps_page.dart';
import 'package:fluttercrud/screens/post_details_page.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final routes = <String, WidgetBuilder>{
LoginPage.tag: (context) => LoginPage(),
HomePage.tag: (context) => HomePage(),
ListPost.tag: (context) => ListPost(),
MapsPage.tag: (context) => MapsPage(),
PostDetailsPage.tag: (context) => PostDetailsPage(new List()),
};
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'iGota',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch : Colors.blue,
),
home: LoginPage(),
routes: routes,
);
}
}
PostDetailsPage.dart class
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
class PostDetailsPage extends StatelessWidget{
static String tag = 'post-details-page';
final List<charts.Series> seriesList;
final bool animate;
PostDetailsPage(this.seriesList, {this.animate});
/// Creates a [BarChart] with sample data and no transition.
factory PostDetailsPage.withSampleData() {
return new PostDetailsPage(
_createSampleData(),
// Disable animations for image tests.
animate: false,
);
}
#override
Widget build(BuildContext context) {
return new charts.BarChart(
seriesList,
animate: animate,
);
}
/// Create one series with sample hard coded data.
static List<charts.Series<OrdinalSales, String>> _createSampleData() {
final data = [
new OrdinalSales('2014', 5),
new OrdinalSales('2015', 25),
new OrdinalSales('2016', 100),
new OrdinalSales('2017', 75),
];
return [
new charts.Series<OrdinalSales, String>(
id: 'Sales',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (OrdinalSales sales, _) => sales.year,
measureFn: (OrdinalSales sales, _) => sales.sales,
data: data,
)
];
}
}
/// Sample ordinal data type.
class OrdinalSales {
final String year;
final int sales;
OrdinalSales(this.year, this.sales);
}
UPDATED 2: FIXED
Main.dart
import 'package:flutter/material.dart';
import 'package:fluttercrud/screens/login_page.dart';
import 'package:fluttercrud/screens/home_page.dart';
import 'package:fluttercrud/screens/partials/list_post.dart';
import 'package:fluttercrud/screens/maps_page.dart';
import 'package:fluttercrud/screens/post_details_page.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final routes = <String, WidgetBuilder>{
LoginPage.tag: (context) => LoginPage(),
HomePage.tag: (context) => HomePage(),
ListPost.tag: (context) => ListPost(),
MapsPage.tag: (context) => MapsPage(),
PostDetailsPage.tag: (context) => PostDetailsPage(),
};
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'iGota',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch : Colors.blue,
),
home: LoginPage(),
routes: routes,
);
}
}
PostDetailsPage.dart
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
class PostDetailsPage extends StatefulWidget {
static String tag = 'post-details-page';
#override
PostDetailsPageState createState() => new PostDetailsPageState();
}
class PostDetailsPageState extends State<PostDetailsPage> {
List<charts.Series> seriesList = [];
bool animate;
void initState() {
super.initState();
seriesList = _createSampleData();
animate = false;
}
#override
Widget build(BuildContext context) {
return new Container(
decoration: new BoxDecoration(color: Colors.white),
child: new charts.BarChart(
seriesList,
animate: animate,
),
);
}
/// Create one series with sample hard coded data.
static List<charts.Series<OrdinalSales, String>> _createSampleData() {
final data = [
new OrdinalSales('2014', 5),
new OrdinalSales('2015', 25),
new OrdinalSales('2016', 100),
new OrdinalSales('2017', 75),
];
return [
new charts.Series<OrdinalSales, String>(
id: 'Sales',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (OrdinalSales sales, _) => sales.year,
measureFn: (OrdinalSales sales, _) => sales.sales,
data: data,
)
];
}
}
/// Sample ordinal data type.
class OrdinalSales {
final String year;
final int sales;
OrdinalSales(this.year, this.sales);
}
You can fix your issue by explicitly declaring the type on the List when you pass it in.
PostDetailsPage.tag: (context) => PostDetailsPage(List<charts.Series<dynamic, String>>()),
Or, you can explicitly declare the type of the list itself on the page itself:
final List<charts.Series<dynamic, String>> seriesList;
I think the intended way for the charts library to work is that you pass in an already initialized list of chart data, which the page then renders. If you want to return just with the given sample data, you should just use the given factory method PostDetailsPage.withSampleData().
Let's say, I have a test for a screen in Flutter using WidgetTester. There is a button, which executes a navigation via Navigator. I would like to test behavior of that button.
Widget/Screen
class MyScreen extends StatefulWidget {
MyScreen({Key key}) : super(key: key);
#override
_MyScreenState createState() => _MyScreenScreenState();
}
class _MyScreenState extends State<MyScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed("/nextscreen");
},
child: Text(Strings.traktTvUrl)
)
)
);
}
}
Test
void main() {
testWidgets('Button is present and triggers navigation after tapped',
(WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home: MyScreen()));
expect(find.byType(RaisedButton), findsOneWidget);
await tester.tap(find.byType(RaisedButton));
//how to test navigator?
});
}
I there a proper way how to check, that Navigator was called? Or is there a way to mock and replace navigator?
Pleas note, that code above will actually fail with an exception, because there is no named route '/nextscreen' declared in application. That's simple to solve and you don't need to point it out.
My main concern is how to correctly approach this test scenario in Flutter.
While what Danny said is correct and works, you can also create a mocked NavigatorObserver to avoid any extra boilerplate:
import 'package:mockito/mockito.dart';
class MockNavigatorObserver extends Mock implements NavigatorObserver {}
That would translate to your test case as follows:
void main() {
testWidgets('Button is present and triggers navigation after tapped',
(WidgetTester tester) async {
final mockObserver = MockNavigatorObserver();
await tester.pumpWidget(
MaterialApp(
home: MyScreen(),
navigatorObservers: [mockObserver],
),
);
expect(find.byType(RaisedButton), findsOneWidget);
await tester.tap(find.byType(RaisedButton));
await tester.pumpAndSettle();
/// Verify that a push event happened
verify(mockObserver.didPush(any, any));
/// You'd also want to be sure that your page is now
/// present in the screen.
expect(find.byType(DetailsPage), findsOneWidget);
});
}
I wrote an in-depth article about this on my blog, which you can find here.
In the navigator tests in the flutter repo they use the NavigatorObserver class to observe navigations:
class TestObserver extends NavigatorObserver {
OnObservation onPushed;
OnObservation onPopped;
OnObservation onRemoved;
OnObservation onReplaced;
#override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
if (onPushed != null) {
onPushed(route, previousRoute);
}
}
#override
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
if (onPopped != null) {
onPopped(route, previousRoute);
}
}
#override
void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
if (onRemoved != null)
onRemoved(route, previousRoute);
}
#override
void didReplace({ Route<dynamic> oldRoute, Route<dynamic> newRoute }) {
if (onReplaced != null)
onReplaced(newRoute, oldRoute);
}
}
This looks like it should do what you want, however it may only work form the top level (MaterialApp), I'm not sure if you can provide it to just a widget.
Inspired by the other posts, this is my 2022 null-safe Mockito-based approach. Imagine I have this helper method I want to unit test:
navigateToNumber(int number, BuildContext context) {
Navigator.of(context).pushNamed(
number.isEven ? '/even' : '/odd'
);
}
It can be tested this way:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
import 'package:my_app/number_route_helper.dart';
import 'number_route_helper_test.mocks.dart';
#GenerateMocks([],
customMocks: [
MockSpec<NavigatorObserver>(returnNullOnMissingStub: true)
])
void main() {
group('NumberRouteHelper', () {
testWidgets('navigateToNumber', (WidgetTester tester) async {
final mockObserver = MockNavigatorObserver();
// "Fake" routes used to verify the right route was pushed
final evenRoute = MaterialPageRoute(builder: (_) => Container());
final oddRoute = MaterialPageRoute(builder: (_) => Container());
await tester.pumpWidget(
MaterialApp(
home: Container(),
navigatorObservers: [mockObserver],
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/even':
return evenRoute;
case '/odd':
return oddRoute;
}
}
),
);
final BuildContext context = tester.element(find.byType(Container));
/// Verify that a push to evenRoute happened
navigateToNumber(2, context);
await tester.pumpAndSettle();
verify(mockObserver.didPush(evenRoute, any));
/// Verify that a push to oddRoute happened
navigateToNumber(3, context);
await tester.pumpAndSettle();
verify(mockObserver.didPush(oddRoute, any));
});
});
}
Just remember you need to have Mockito installed, as described here: https://pub.dev/packages/mockito
This is modified version of the other answer to show how to do it with mocktail instead of mockito:
import 'package:mocktail/mocktail.dart';
class MockNavigatorObserver extends Mock implements NavigatorObserver {}
class FakeRoute extends Fake implements Route {}
void main() {
setUpAll(() {
registerFallbackValue(FakeRoute());
});
testWidgets('Button is present and triggers navigation after tapped',
(WidgetTester tester) async {
final mockObserver = MockNavigatorObserver();
await tester.pumpWidget(
MaterialApp(
home: MyScreen(),
navigatorObservers: [mockObserver],
),
);
expect(find.byType(RaisedButton), findsOneWidget);
await tester.tap(find.byType(RaisedButton));
await tester.pumpAndSettle();
verify(mockObserver.didPush(any(), any()));
expect(find.byType(DetailsPage), findsOneWidget);
});
}
Following solution is, let's say, a general approach and it's not specific to Flutter.
Navigation could be abstracted away from a screen or a widget. Test can mock and inject this abstraction. This approach should be sufficient for testing such behavior.
There are several ways how to achieve that. I will show one of those, for purpose of this response. Perhaps it's possible to simplify it a bit or to make it more "Darty".
Abstraction for navigation
class AppNavigatorFactory {
AppNavigator get(BuildContext context) =>
AppNavigator._forNavigator(Navigator.of(context));
}
class TestAppNavigatorFactory extends AppNavigatorFactory {
final AppNavigator mockAppNavigator;
TestAppNavigatorFactory(this.mockAppNavigator);
#override
AppNavigator get(BuildContext context) => mockAppNavigator;
}
class AppNavigator {
NavigatorState _flutterNavigator;
AppNavigator._forNavigator(this._flutterNavigator);
void showNextscreen() {
_flutterNavigator.pushNamed('/nextscreen');
}
}
Injection into a widget
class MyScreen extends StatefulWidget {
final _appNavigatorFactory;
MyScreen(this._appNavigatorFactory, {Key key}) : super(key: key);
#override
_MyScreenState createState() => _MyScreenState(_appNavigatorFactory);
}
class _MyScreenState extends State<MyScreen> {
final _appNavigatorFactory;
_MyScreenState(this._appNavigatorFactory);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () {
_appNavigatorFactory.get(context).showNextscreen();
},
child: Text(Strings.traktTvUrl)
)
)
);
}
}
Example of a test (Uses Mockito for Dart)
class MockAppNavigator extends Mock implements AppNavigator {}
void main() {
final appNavigator = MockAppNavigator();
setUp(() {
reset(appNavigator);
});
testWidgets('Button is present and triggers navigation after tapped',
(WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home: MyScreen(TestAppNavigatorFactory())));
expect(find.byType(RaisedButton), findsOneWidget);
await tester.tap(find.byType(RaisedButton));
verify(appNavigator.showNextscreen());
});
}