I have successfully implemented the flutter_secure_storage in my flutter project, when the user writes his email and password, it get's stored, but here is the thing I don't understand. How should I setup screen routes depending if the user has already logged in or not. If it is the same user, so the username and pass are stored in the secure_storage, I want him to go directly to HomeScreen(), but if there is a new user that needs to log in, so there is no data in the secure_storage, then I want him sent to LoginScreen(). I have done this so far:
import 'dart:async';
import 'package:flutter/material.dart';
import 'login_screen.dart';
import 'home_screen.dart';
import 'components/alarm_buttons.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class WelcomeScreen extends StatefulWidget {
static const String id = 'welcome_screen';
#override
_WelcomeScreenState createState() => _WelcomeScreenState();
}
class _WelcomeScreenState extends State<WelcomeScreen> {
void readData() async {
final storage = FlutterSecureStorage();
String myPassword = await storage.read(key: "p");
String myEmail = await storage.read(key: "e");
print(myEmail);
print(myPassword);
}
#override
void initState() {
final storage = FlutterSecureStorage();
Timer(
Duration(seconds: 2),
() => Navigator.pushNamed(
context,
storage == null ? LoginScreen.id : HomePage.id,
));
readData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
AlarmButtons(
buttonColour: Colors.grey,
buttonText: 'Log In',
buttonTextColour: Colors.white,
onButtonPress: () {
Navigator.pushNamed(context, LoginScreen.id);
},
),
AlarmButtons(
buttonColour: Colors.white,
buttonText: 'Sign up',
buttonTextColour: Colors.grey,
onButtonPress: () {
Navigator.pushNamed(context, SignUpScreen.id);
},
),
],
),
),
);
}
}
Now the problem starts when I want to return to the Welcome Screen (the starting page of my app shown above), naturally it triggers the initState again and I get back to the HomePage() again. How can I dispose of that, only triggering that initState when the app starts, so after automatic login I can return to the Welcome Screen without triggering it?
Thanks in advance!
You should initial start something like a SplashScreen (or WelcomeScreen in your case). There you can decide to which screen you want to navigate based on the saved data. Example:
class SplashScreen extends StatefulWidget {
const SplashScreen({Key key}) : super(key: key);
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
_startApp();
super.initState();
}
Future<void> _startApp() async {
final storage = FlutterSecureStorage();
String myEmail = await storage.read(key: "e");
if (myEmail == null) {
// TODO Navigate to Login Screen
} else {
// TODO Navigate to Home Screen
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("Splashscreen"),
),
);
}
}
Related
I am working in Riverpod Auth flow boilerplate application.
I want to use common loading screen for all async function even login and logout. Currently I have AppState provider if Appstate loading i show loading screen. it's working fine for login but i wonder it’s good way or bad way.
Can i use this loading screen for all async task in the App?
AuthWidget:
class AuthWidget extends ConsumerWidget {
const AuthWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
AppState appState = ref.watch(appStateProvider);
if(appState.isLoading){
return const Center(child: CircularProgressIndicator(color: Colors.red),);
}
return appState.isAuthenticated ? const HomePage() : const SignIn();
}
}
AppState:
class AppState {
User? user;
bool isLoading;
bool isAuthenticated;
AppState(this.user, this.isLoading, this.isAuthenticated);
}
AuthRepository:
class AuthRepository extends StateNotifier<AppState>{
AuthRepository() : super(AppState(null,false,false));
Future<void> signIn()async {
state = AppState(null,true,false);
await Future.delayed(const Duration(seconds: 3));
User user = User(userName: 'FakeUser', email: 'user#gmail.com');
AppState appState = AppState(user, false, true);
state = appState;
}
}
final appStateProvider = StateNotifierProvider<AuthRepository,AppState>((ref){
return AuthRepository();
});
To answer your question : Yes you can.
The only thing I'd change here is the content of your AppState : I'd use a LoadingState dedicated to trigger your Loader instead.
Here is how I like to manage screens with a common loader in my apps.
1 - Create a LoadingState and provide it
final loadingStateProvider = ChangeNotifierProvider((ref) => LoadingState());
class LoadingState extends ChangeNotifier {
bool isLoading = false;
void startLoader() {
if (!isLoading) {
isLoading = true;
notifyListeners();
}
}
void stopLoader() {
if (isLoading) {
isLoading = false;
notifyListeners();
}
}
}
2 - Define a base page with the "common" loader
class LoadingContainer extends ConsumerWidget {
const LoadingContainer({
Key? key,
required this.child,
}) : super(key: key);
final Widget child;
#override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(loadingStateProvider);
return Stack(
children: [
child,
if (state.isLoading)
const Center(child: CircularProgressIndicator())
else
const SizedBox(),
],
);
}
}
3 - Implement this widget whenever I need to handle loading datas.
return Scaffold(
backgroundColor: AppColor.blue,
body: LoadingContainer(
child: ...
And then I simply have to update my loadingStateProvider and it's isLoading value from a Controller or the Widget directly
If you want a centralized/common async calls, the InheritedWidget is ideal for that, you can just add a method and call it from anywhere down stream and because the call is offloaded with async, you can attach extra arguments and add usefull functionality such as a live update instead of relying on stuff like .then(). This example might not be as simple as FDuhen's but you can mix them together if you want to not use keys
AppState now is a widget and contains trigers that rely on global keys to rebuild the correct components, here i assumed that you actualy want to have an common overlay and not a loading screen widget, if not using a Navigator would be batter
Using keys is specially good if you end up implementing something this line, <token> been just a number that references a group of widgets
key: AppState.of(ctx).rebuild_on_triger(<token>)
class App_State_Data {
GlobalKey? page_key;
bool is_logged = false;
bool loading_overlay = false;
String loading_message = '';
}
class AppState extends InheritedWidget {
final App_State_Data _state;
bool get is_logged => _state.is_logged;
bool get should_overlay => _state.loading_overlay;
String get loading_message => _state.loading_message;
void page_rebuild() {
(_state.page_key!.currentState as _Page_Base).rebuild();
}
GlobalKey get page_key {
if (_state.page_key == null) {
_state.page_key = GlobalKey();
}
return _state.page_key!;
}
void place_overlay(String msg) {
_state.loading_message = msg;
_state.loading_overlay = true;
page_rebuild();
}
void clear_overlay() {
_state.loading_message = '';
_state.loading_overlay = false;
page_rebuild();
}
Future<void> triger_login(String message) async {
place_overlay(message);
await Future.delayed(const Duration(seconds: 2));
_state.is_logged = true;
clear_overlay();
}
Future<void> triger_logout(String message) async {
place_overlay(message);
await Future.delayed(const Duration(seconds: 1));
_state.is_logged = false;
clear_overlay();
}
AppState({Key? key, required Widget child})
: this._state = App_State_Data(),
super(key: key, child: child);
static AppState of(BuildContext ctx) {
final AppState? ret = ctx.dependOnInheritedWidgetOfExactType<AppState>();
assert(ret != null, 'No AppState found!');
return ret!;
}
#override
bool updateShouldNotify(AppState old) => true;
}
Here i added it as the topmost element making it like a global data class with is not necessary, you can split the state content and add just the necessary to where its needed
void main() => runApp(AppState(child: App()));
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext ctx) {
return MaterialApp(
home: Scaffold(
body: Page_Base(
key: AppState.of(ctx).page_key,
),
),
);
}
}
class Page_Base extends StatefulWidget {
final GlobalKey key;
const Page_Base({
required this.key,
}) : super(key: key);
#override
_Page_Base createState() => _Page_Base();
}
class _Page_Base extends State<Page_Base> {
Widget build_overlay(BuildContext ctx) {
return Center(
child: Container(
width: double.infinity,
height: double.infinity,
color: Color(0xC09E9E9E),
child: Center(
child: Text(AppState.of(ctx).loading_message),
),
),
);
}
#override
Widget build(BuildContext ctx) {
return Stack(
children: [
AppState.of(ctx).is_logged ? Page_Home() : Page_Login(),
AppState.of(ctx).should_overlay ? build_overlay(ctx) : Material(),
],
);
}
void rebuild() {
// setState() is protected and can not be called
// from outside of the this. scope
setState(() => null);
}
}
Using AppState is the best part, just because the widget does not have to call more than 1 function and it will rebuild with the correct data on complition
class Page_Login extends StatelessWidget {
const Page_Login({Key? key}) : super(key: key);
#override
Widget build(BuildContext ctx) {
return Center(
child: InkWell(
onTap: () => AppState.of(ctx).triger_login('Login'),
child: Container(
width: 200,
height: 200,
color: Colors.greenAccent,
child: Text('Page_Login'),
),
),
);
}
}
class Page_Home extends StatelessWidget {
const Page_Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext ctx) {
return Center(
child: InkWell(
onTap: () => AppState.of(ctx).triger_logout('Logout'),
child: Container(
width: 200,
height: 200,
color: Colors.blueAccent,
child: Text('Page_Home'),
),
),
);
}
}
Global loading indicator
If you want a centralized loading indicator to use in your whole app you could take advantage of Overlay's, which flutter already uses for dialogs, popups, bottom sheets etc. This way we don't introduce new widget in the widget tree.
If you only want to toggle between loading states you can use a StateProvider to handle the simple boolean value, else you could create a State/Change Notifier. This way you decouple your loading state from your AppState
final loadingProvider = StateProvider<bool>((ref) => false);
void main() => runApp(const ProviderScope(child: MaterialApp(home: GlobalLoadingIndicator(child: Home()))));
// This widget should wrap your entire app, but be below MaterialApp in order to have access to the Overlay
class GlobalLoadingIndicator extends ConsumerStatefulWidget {
final Widget child;
const GlobalLoadingIndicator({required this.child, Key? key}) : super(key: key);
#override
ConsumerState createState() => _GlobalLoadingIndicatorState();
}
class _GlobalLoadingIndicatorState extends ConsumerState<GlobalLoadingIndicator> {
//We need to cache the overlay entries we are showing as part of the indicator in order to remove them when the indicator is hidden.
final List<OverlayEntry> _entries = [];
#override
Widget build(BuildContext context) {
ref.listen<bool>(loadingProvider, (previous, next) {
// We just want to make changes if the states are different
if (previous == next) return;
if (next) {
// Add a modal barrier so the user cannot interact with the app while the loading indicator is visible
_entries.add(OverlayEntry(builder: (_) => ModalBarrier(color: Colors.black12.withOpacity(.5))));
_entries.add(OverlayEntry(
builder: (_) =>const Center(
child: Card(child: Padding(padding: EdgeInsets.all(16.0), child: CircularProgressIndicator())))));
// Insert the overlay entries into the overlay to actually show the loading indicator
Overlay.of(context)?.insertAll(_entries);
} else {
// Remove the overlay entries from the overlay to hide the loading indicator
_entries.forEach((e) => e.remove());
// Remove the cached overlay entries from the widget state
_entries.clear();
}
});
return widget.child;
}
}
We insert the GlobalLoadingIndicator high up in the widget tree although anywhere below the MaterialApp is fine (as long as it can access the Overlay via context).
The GlobalLoadingIndicator wont create extra widgets in the widget tree, and will only manage the overlays, here I add two overlays, one is a ModalBarrier which the user from interacting with widgets behind itself. And the other the actual LoadingIndicator. You are free to not add the ModalBarrier, or make it dismissible (or even if you decide to create a more complex loadingProvider, customize it in case you need to cater different use cases).
A sample usage after you have this set up is just switching the state of the loadingProvider, most of the times you would do this programatically, but for interactiveness I'll use a Switch :
class Home extends ConsumerWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, ref) {
final isLoading = ref.watch(loadingProvider);
return Scaffold(
appBar: AppBar(),
body: Center(
child: SwitchListTile(
value: isLoading,
onChanged: (value) {
ref.read(loadingProvider.notifier).state = value;
Future.delayed(const Duration(seconds: 4)).then((value) {
ref.read(loadingProvider.notifier).state = false;
});
},
title: const FlutterLogo(),
),
));
}
}
You can fiddle with this snippet in dartpad
Result:
Per Screen/Section loading indicator
As a side note when displaying loading states inside components of the app I recommend you to use an AnimatedSwitcher , as it fades between the widgets , super handy when dealing with screens which can change content abruptly.
final loadingProvider = StateProvider<bool>((ref) => false);
void main() => runApp(ProviderScope(child: MaterialApp(home: Home())));
class Home extends ConsumerWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, ref) {
final isLoading = ref.watch(loadingProvider);
return Scaffold(
appBar: AppBar(),
body: Center(
child: SwitchListTile(
value: isLoading,
onChanged: (value) {
ref.read(loadingProvider.notifier).state = value;
},
title: AnimatedSwitcher(
duration: Duration(milliseconds: 400),
child: isLoading?CircularProgressIndicator():FlutterLogo()
),
),
));
}
}
I am trying to get user information from an API. For this, I created a user object. I want to call the function and store it in a user. But the problem is, that I cannot use await and wait till all the data is there.
So instead of async and await, I tried to use .then and fill userInfo with the data. But now the email value is not showing. It is showing 'loading...'.
If I use Future I cannot do user.email.
Is it better to use FutureBuilder? Or try and use Async and Await (the call takes 2.5 seconds)
Here is the code
class Addressbook extends StatefulWidget {
const Addressbook({Key? key}) : super(key: key);
#override
State<Addressbook> createState() => _AddressbookState();
}
class _AddressbookState extends State<Addressbook> {
User? userInfo;
Future<User> getUserInformation() async {
User user = await UserService().getUserById(12345);
return user;
}
#override
void initState() {
// TODO: implement initState
getUserInformation().then((response) {
userInfo = response;
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Mijn adresboek'),
centerTitle: true,
elevation: 0.5,
titleTextStyle: const TextStyle(
fontSize: 21,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
body: Text(userInfo?.email ?? "loading..."),
);
}
}
What do I want to archive?
I want to display user data on the screen. In this case I want to show the email.
What is the problem?
User is not filled with data on init (probably because it is still loading to get the data).
My question
How can I solve this problem? Is async await a solution or should I use FutureBuilder? Can you give me a working sample?
Thanks for helping!
You can use futurebuilder or you can use in initstate to fetch api
#override
void initState() {
// TODO: implement initState
Future.delayed(Duration(seconds: 10), () {
setState(() {
userinfo = "response";
});
// userinfo = "response";
});
super.initState();
}
Inside the widget
Text(userinfo != null ? userinfo.toString() : "loading..."),
SAmple Code
import 'package:flutter/material.dart';
//import 'package:pucon/home.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String? userinfo = null;
String? userinfo2 = null;
#override
void initState() {
// TODO: implement initState
Future.delayed(Duration(seconds: 10), () {
setState(() {
userinfo = "response";
});
// userinfo = "response";
});
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: ListView(
shrinkWrap: true,
children: [
Text(""),
Text(""),
Text(userinfo != null ? userinfo.toString() : "loading..."),
Row(
children: [
FutureBuilder(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Container(
child: Text(userinfo2.toString()), );
} else {
return SizedBox(
child: CircularProgressIndicator(),
height: 45,
width: 45,
);
// else
// return Container(
// child: CircularProgressIndicator(),
// height: 45,
// width: 45,
// );
}
},
future: _future(),
),
],
)
// InsertData(),
],
),
);
}
_future() async {
await Future.delayed(Duration(seconds: 5), () {
userinfo2 = "Completed";
// setState(() {
//
// });
// userinfo = "response";
});
}
}
Let's say I create a new screen team_screen which is the first parent of the tree.
Now for my team screen there are many widgets, some of theme have their own request, I want to show loader until every widget/request finished and ready.
I thought on 2 approaches.
All the requests are executed in team_screen with future builder and I pass the props to my widgets by demand.
Every widget with request get function that get executed in the async function in the initState function, then in my parent I make to every widget state parameter that is equal to true by the function I passed and when all is don't I stop the loader.
To sum up my problem is how to maintain a widget with many children and requests and showing one loader for entire page, making all the request on same widget? Pass isInitialize function to every widget?.
Which approach is better and if there are more approaches, I would like to hear.
Thank you for your help
Example for the second approach:
import 'package:flutter/material.dart';
import 'package:info_striker/locator.dart';
import 'package:info_striker/models/fixture/fixture.dart';
import 'package:info_striker/models/odds/bookmaker.dart';
import 'package:info_striker/models/synced-team/synced_team.dart';
import 'package:info_striker/services/fixture_service.dart';
import 'package:info_striker/utils/date_utilities.dart';
class TeamNextMatch extends StatefulWidget {
Function isInitialized;
SyncedTeam team;
TeamNextMatch({
Key? key,
required this.isInitialized,
required this.team,
}) : super(key: key);
#override
State<TeamNextMatch> createState() => _TeamNextMatchState();
}
class _TeamNextMatchState extends State<TeamNextMatch> {
Fixture? _fixture;
Bookmaker? _matchResult;
bool _isInitialized = false;
#override
void initState() {
super.initState();
init();
}
init() async {
final response = await locator<FixturesService>().getData(widget.team.id);
if (response != null) {
setState(() {
_fixture = Fixture.fromMap(response["fixture"]);
_matchResult = Bookmaker.fromMap(response["matchResultOdds"]);
});
}
widget.isInitialized(true);
}
#override
Widget build(BuildContext context) {
String? _date;
bool show = _fixture != null && _matchResult != null;
_fixture != null ? "${DateUtilities.getShortDateString(_fixture!.date)}, ${DateUtilities.getTimeString(_fixture!.date)}" : null;
return show
? Column(
children: [
Text(_fixture?.league?["name"]),
if (_date != null) Text(_date),
],
)
: const SizedBox();
}
}
You can show loader as described below -
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_application_1/data_model.dart';
import 'package:http/http.dart' as http;
class APiTest extends StatefulWidget {
const APiTest({Key? key}) : super(key: key);
#override
_APiTestState createState() => _APiTestState();
}
class _APiTestState extends State<APiTest> {
final String _url = "https://jsonplaceholder.typicode.com/todos/";
bool _isLoading = true;
final List<DataModel> _allData = [];
#override
void initState() {
super.initState();
_initData().then((value) {
setState(() {
_isLoading = false;
});
});
}
Future<void> _initData() async {
final response = await http.get(Uri.parse(_url));
final List res = jsonDecode(response.body);
res.forEach((element) {
_allData.add(DataModel.fromJson(element));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Loading Demo"),
),
body: Stack(
children: [
ListView.separated(
itemCount: _allData.length,
controller: ScrollController(),
separatorBuilder: (_, __) => const SizedBox(height: 10),
itemBuilder: ((context, index) {
return ListTile(
tileColor: Colors.grey[200],
title: Text(_allData[index].title!),
subtitle: Text(_allData[index].id.toString()),
);
}),
),
if (_isLoading)
const Center(
child: CircularProgressIndicator(),
)
],
),
);
}
}
I am trying to apply conditions on widget when user is logged in, display widget logout else display login button.How is it possible?
Before login
After Login
I used shred_preferences package saving, getting and removing used data.
Here my codes:
shared_preferences
import 'package:shared_preferences/shared_preferences.dart';
class PrefServices{
Future createCache(String username, String password) async {
SharedPreferences _preferences = await SharedPreferences.getInstance();
_preferences.setString("username", username);
_preferences.setString("password", password);
}
Future readCache(String username) async {
SharedPreferences _preferences = await SharedPreferences.getInstance();
var username = _preferences.getString("username")?? "null";
// _preferences.getString("password");
return username;
}
Future<void> removeCache(String username, String password) async {
SharedPreferences _preferences = await SharedPreferences.getInstance();
_preferences.remove("username");
_preferences.remove("password");
}
}
Profile Screen
import 'dart:async';
import 'package:clothing_roll/shred_preferences/shred_preferences_services.dart';
import 'package:clothing_roll/ui/widget/profile/login_widget.dart';
import 'package:clothing_roll/ui/widget/profile/profiles_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class ProfileScreen extends StatefulWidget {
const ProfileScreen({ Key? key }) : super(key: key);
#override
_ProfileScreen createState() => _ProfileScreen();
}
class _ProfileScreen extends State<ProfileScreen> {
final PrefServices _prefServices = PrefServices();
#override
void initState() {
_prefServices.readCache("username").then((value) {
print(value.toString());
if (value != null) {
return Timer(Duration(seconds: 1),
() => ProfileWidget());
} else {
return Timer(Duration(seconds: 1),
() => LoginWidget());
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
Note: I have used Getx package
create a bool variable for conmdition check
add ternary operator to bool variable and change the conditions
class Test1 extends StatelessWidget {
Controller controller = Get.put(Controller());
Test1({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
width: Get.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
controller.isLogged.value = !controller.isLogged.value;
},
child: Text("Press to change value")),
Obx(
() => controller.isLogged.value
? ElevatedButton(onPressed: () {}, child: Text("logged In"))
: ElevatedButton(onPressed: () {}, child: Text("Logged Out")),
),
],
),
),
);
}
}
I have searched the web for auto refresh widgets and auto refresh future JSON for Flutter but the results all seem to be for pull down refresh.
What I need is like a function that I can call and every minute that said function repeats.
I know I have to integrate something like:
var future = new Future.delayed(const Duration(milliseconds: 10), TracksWidget());
However I am not sure where I need to put it.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../model/track.dart';
class TracksWidget extends StatefulWidget {
#override
_TracksWidgetState createState() => _TracksWidgetState();
}
class _TracksWidgetState extends State<TracksWidget> {
Future<Track> track;
#override
Widget build(BuildContext context) {
double c_width = MediaQuery.of(context).size.width;
return new FutureBuilder<Track>(
future: track,
builder: (context, snapshot) {
if (snapshot.hasData) {
Track track = snapshot.data;
return new Container(
padding: const EdgeInsets.all(16.0),
width: c_width,
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.network(track.imageurl, width:200.0, height: 200.0,fit: BoxFit.cover),
Text(track.title),
Text(track.artist),
]),
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
//By default, show a loading spinner.
return CircularProgressIndicator();
},
);
}
#override
void initState() {
super.initState();
track = fetchTrack();
}
Future<Track> fetchTrack() async {
final response =
await http.get('http://139.59.108.222:2199/rpc/drn1/streaminfo.get');
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON.
var responseJson = json.decode(response.body);
// assume there is only one track to display
// SO question mentioned 'display current track'
var track = responseJson['data']
.map((musicFileJson) => Track.fromJson(musicFileJson['track']))
.first;
return track;
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
}
The simplest way to do this I have found is to use the Timer function.
If you put the timer into initState it will start when the the app is started.
In the code below, the timer will call the addValue() method every 5 seconds which increases the value by one each time. Just remember to dispose of the timer when you have finished with it.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Timer timer;
int counter = 0;
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 5), (Timer t) => addValue());
}
void addValue() {
setState(() {
counter++;
});
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(counter.toString())
],
),
),
);
}
}