I have this class:
import 'package:flutter/material.dart';
class AgeText extends StatelessWidget {
final String dateOfBirth;
const AgeText({Key key, #required this.dateOfBirth}) : super(key: key);
#override
Widget build(BuildContext context) {
final age = _calculateAge();
return Text(age.toString());
}
int _calculateAge() {
final dateOfBirthDate = DateTime.parse(dateOfBirth);
final difference = DateTime.now().difference(dateOfBirthDate);
final age = difference.inDays / 365;
return age.floor();
}
}
I'd like to test that it produces the correct age when a date of birth is passed into it. What is the best way to do this in Flutter?
SOLUTION: For those interested, here's the solution using #Günter Zöchbauer's suggestion of the clock package.
My widget class:
import 'package:flutter/material.dart';
import 'package:clock/clock.dart';
class AgeText extends StatelessWidget {
final String dateOfBirth;
final Clock clock;
const AgeText({Key key, #required this.dateOfBirth, this.clock = const Clock()}) : super(key: key);
#override
Widget build(BuildContext context) {
final age = _calculateAge();
return Text(age.toString());
}
int _calculateAge() {
final dateOfBirthDate = DateTime.parse(dateOfBirth);
final difference = clock.now().difference(dateOfBirthDate);
final age = difference.inDays / 365;
return age.floor();
}
}
and my test class:
import 'package:clock/clock.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/age.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets("shows age 30 when date of birth is 30 years ago", (WidgetTester tester) async {
final mockClock = Clock.fixed(DateTime(2000, 01, 01));
final testableWidget = MaterialApp(
home: AgeText(
dateOfBirth: "1970-01-01T00:00:00",
clock: mockClock,
),
);
await tester.pumpWidget(testableWidget);
expect(find.text("30"), findsOneWidget);
});
}
As Günter said, the clock package, maintained by the Dart team, provides a very neat way to achieve this.
Normal usage:
import 'package:clock/clock.dart';
void main() {
// prints current date and time
print(clock.now());
}
Overriding the current time:
import 'package:clock/clock.dart';
void main() {
withClock(
Clock.fixed(DateTime(2000)),
() {
// always prints 2000-01-01 00:00:00.
print(clock.now());
},
);
}
I wrote about this in more detail on my blog.
For widget tests, you need to wrap pumpWidget, pump and expect in the withClock callback.
If you use the clock package for code depending on DateTime.now() you can easily mock it.
Other than creating a custom wrapper around DateTime.now(), I don't think there is a better way than what the clock package provides.
As mentioned here: https://stackoverflow.com/a/63073876/2235274 implement extension on DateTime.
extension CustomizableDateTime on DateTime {
static DateTime _customTime;
static DateTime get current {
return _customTime ?? DateTime.now();
}
static set customTime(DateTime customTime) {
_customTime = customTime;
}
}
Then just use CustomizableDateTime.current in the production code. You can modify the returned value in tests like that: CustomizableDateTime.customTime = DateTime.parse("1969-07-20 20:18:04");. There is no need to use third party libraries.
Related
I just want to take from outside A function that like "Future<List?> Function(String userId)" then use it, but this error appear, this tell me I try to "equalize" (if this word don't fit please edit, and title is not fitted), No I dont want to equalize, I just want to run this function and take value. You can run this code on https://dartpad.dev/?
A value of type 'Future<List<String>?> Function(String)' can't be assigned to a variable of type 'List<String>?'.
Try changing the type of the variable, or casting the right-hand type to 'List<String>?'.
--
import 'package:flutter/material.dart';
typedef getAllProducts = Future<List<String>?> Function(String userId);
class AdanaView extends StatefulWidget {
const AdanaView({Key? key, required this.getProducts}) : super(key: key);
final getAllProducts getProducts;
#override
State<AdanaView> createState() => _AdanaViewState();
}
class _AdanaViewState extends State<AdanaView> {
List<String>? products;
#override
void initState() {
super.initState();
getAll();
}
Future<void> getAll() async {
products = await widget.getProducts;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Adana'),
),
);
}
}
I am having big time trouble with getting the data from my method getAllPlayerData() outside the .then((Value).
The "bad print" in my code shows up before my "good print". I want to be able to use my variable "TheStats" in my code with all the data inside of it and not a "null" value or "instance of Future<Dynamic"
import 'package:cleanmaybe/Controllers/players_controller.dart';
import 'package:flutter/material.dart';
class PlayerStatView extends StatefulWidget {
const PlayerStatView({Key? key}) : super(key: key);
#override
State<PlayerStatView> createState() => _PlayerStatViewState();
}
class _PlayerStatViewState extends State<PlayerStatView> {
var theStats;
#override
void initState() {
getAllPlayerData().then((value) {
theStats = value;
print("Good print $theStats");
});
super.initState();
}
#override
Widget build(BuildContext context) {
print("bad print $theStats");
return Container();
}
}
This is my "getAllPlayerData()"
getAllPlayerData() async{
SharedPreferences sp = await _pref;
int? id = sp.getInt('idPlayer');
final statAccess = DatabaseModel();
var lookForData = await statAccess.getPlayerData(id);
return lookForData;
}
And if you need it, this is my getPlayerData(id) method that fetches all the data from my database:
Future getPlayerData(idPlayer) async {
var dbCoach = await db;
var dataPlayer = dbCoach!.rawQuery("SELECT * FROM players WHERE"
" idPlayer = '$idPlayer'");
return dataPlayer;
}
Your "bad print" will practically always run before "Good print" as long as getAllPlayerData() take any time at all to process with this construct.
There are several ways to handle this, but a suggestion is to use the FutureBuilder widget for it (as #pskink just wrote as a comment :)) Check the offical documentation.
I'm trying to test a widget that receives and displays some data. This widget uses a controller. In the constructor I start receiving data, after which I execute the parser in a separate isolate. During the tests, the function passed to the compute is not executed until the end, and the widget state does not change. In fact, the structure of the widget looks a little more complicated, but I wrote smaller widget that saves my problem:
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rxdart/rxdart.dart';
class TestObj {
int id;
String name;
String number;
TestObj(this.id, this.name, this.number);
static List<TestObj> jsonListParser(String data) {
List mapObjs = json.decode(data) as List;
if (mapObjs.isEmpty) return [];
List<TestObj> testObjs = [];
for (final Map mapObj in mapObjs as List<Map>)
testObjs.add(
TestObj(
mapObj['id'] as int,
mapObj['name'] as String,
mapObj['number'] as String,
),
);
return testObjs;
}
}
class TestController {
final BehaviorSubject<List<TestObj>> testSubj;
final String responseBody =
'[{"id":2,"number":"1","name":"Объект 1"},{"id":1,"number":"2","name":"Объект 2"}]';
TestController(this.testSubj) {
getData(responseBody, testSubj);
}
Future<void> getData(
String responseBody, BehaviorSubject<List<TestObj>> testSubj) async {
List<TestObj> data = await compute(TestObj.jsonListParser, responseBody);
testSubj.sink.add(data);
}
}
class TestWidget extends StatelessWidget {
final BehaviorSubject<List<TestObj>> testSubj;
final TestController controller;
const TestWidget(this.testSubj, this.controller);
#override
Widget build(BuildContext context) {
return StreamBuilder<List<TestObj>>(
stream: testSubj.stream,
builder: (context, snapshot) => snapshot.data == null
? const CircularProgressIndicator()
: ListView.builder(
itemBuilder: (context, index) => Text(snapshot.data[index].name),
),
);
}
}
void main() {
testWidgets('example test', (tester) async {
final BehaviorSubject<List<TestObj>> testSubj =
BehaviorSubject.seeded(null);
final TestController testController = TestController(testSubj);
await tester.pumpWidget(
TestWidget(testSubj, testController),
);
expect(find.byType(CircularProgressIndicator), findsNothing);
});
}
I have tried using tester.pump, tester.pumpAndSettle (crashed by timeout) and tester.runAsync, but so far without success. What are the solutions of this problem?
As indicated in runAsync docs, it is not supported to have isolates/compute in tests that are proceeded by pump().
To make a self-contained solution, check if you run in test environment or not in your code and skip isolates when you run in a test:
import 'dart:io';
if (!kIsWeb && Platform.environment.containsKey('FLUTTER_TEST')) {
calc()
} else {
calcInIsolate()
}
I wanted to implements a simple tutorial of how parsing an Rss Feed with Flutter, here is my code:
import 'package:flutter/material.dart';
import 'package:webfeed/webfeed.dart';
import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
import 'package:cached_network_image/cached_network_image.dart';
class RSSParser extends StatefulWidget {
#override
_RSSParserState createState() => _RSSParserState();
}
class _RSSParserState extends State<RSSParser> {
final String url = "https://www.90min.com/posts.rss";
RssFeed _feed;
String _title;
static const String loadingFeedMsg = 'Loading Feed...';
static const String feedLoadErrorMsg = 'Error Loading Feed.';
static const String feedOpenErrorMsg = 'Error Opening Feed.';
Future<RssFeed> loadFeed() async{
try{
final client = http.Client();
final response = await client.get(url);
return RssFeed.parse(response.body);
}
catch(e){
}
return null;
}
updateTitle(title){
setState(() {
_title = title;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
updateTitle(widget.title);
}
updateFeed(feed){
setState(() {
_feed = feed;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_title),
),
);
}
}
The problem is that i got a compilation error in that instruction
updateTitle(widget.title);
with the following error message:
The getter 'title' isn't defined for the type 'RSSParser'
In the tutorial, it works fine!!
Do you have an idea how to solve this?
Thank you
You haven't declared title for you RSS widget. It should look something like ths:
class RSSParser extends StatefulWidget {
final String title;
const RSSParser({required this.title});
This should solve your error.
This is not working because there is not title in RSS class.
I think you are not clear with use of widget.something. It means that in the class which extends StatefulWidget there is a something parameter which i need to get in stateObject.
See the code to understand.
class YellowBird extends StatefulWidget {
const YellowBird({ Key? key }) : super(key: key);
String someData = 'SomeData'; // Some data
#override
_YellowBirdState createState() => _YellowBirdState();
}
//This is the state object
class _YellowBirdState extends State<YellowBird> {
// Now that if you need some data from the above class. You use use this widget.someData to get it here
String getHere = widget.someData ;
#override
Widget build(BuildContext context) {
return Container(color: const Color(0xFFFFE306));
}
}
I've been developing an application for both iOS and Android.
The code below makes real-time screen which shows current time.
1.I confirmed that DateTime.now() is working on both OS.
2.Also i confirmed that it is working at the actual Android device and Android emulator.
But everytime when i am trying to test on iOS(both emulator and actual device), _timeString always get a null.
What is wrong with this code? I don't get it.
Here is my environment in advance for solving my question.
VMware workstation 15 Player, Xcode 10.3, Android Studio 3.4.2
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:intl/intl.dart';
class TableLayout extends StatefulWidget {
#override
_TableLayoutState createState() => _TableLayoutState();
}
class _TableLayoutState extends State<TableLayout> {
String _timeString;
String _formatDateTime(DateTime dateTime) {
return DateFormat('MM/dd/yyyy HH:mm:ss').format(dateTime);
}
#override
void initState() {
_timeString = _formatDateTime(DateTime.now());
Timer.periodic(Duration(milliseconds: 1000), (Timer t) => _getTime());
super.initState();
}
void _getTime() {
DateTime _now = DateTime.now();
final String formattedDateTime = _formatDateTime(_now);
if (this.mounted) {
setState(() {
_timeString = formattedDateTime;
});
}
}
#override
Widget build(BuildContext context) {
return
Scaffold(
body:
Container(
child:
Text("\n\n\n\n${_timeString}")
)
);
}
}
Try this by changing date format
To
MM/dd/yyyy hh:mm:ss
I ran your code without any change. (Added a entry point however)
and it works as expected.
Any additional information regarding the intl package will be helpful. Here is my entire app and the result of that below.
My dependency is intl: ^0.15.8.
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:intl/intl.dart';
void main()
{
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
home: TableLayout(),
);
}
}
class TableLayout extends StatefulWidget {
#override
_TableLayoutState createState() => _TableLayoutState();
}
class _TableLayoutState extends State<TableLayout> {
String _timeString;
String _formatDateTime(DateTime dateTime) {
return DateFormat('MM/dd/yyyy HH:mm:ss').format(dateTime);
}
#override
void initState() {
_timeString = _formatDateTime(DateTime.now());
Timer.periodic(Duration(milliseconds: 1000), (Timer t) => _getTime());
super.initState();
}
void _getTime() {
DateTime _now = DateTime.now();
final String formattedDateTime = _formatDateTime(_now);
if (this.mounted) {
setState(() {
_timeString = formattedDateTime;
});
}
}
#override
Widget build(BuildContext context) {
return
Scaffold(
body:
Container(
child:
Text("\n\n\n\n${_timeString}")
)
);
}
}
App running on IOS simulator