Flutter getx json - json

First of all, I apologize to everyone for my low level of English. I want to make an application for a live broadcasting radio station. json link of radio site "http://radyo.comu.edu.tr/json/". This link is refreshed every 5 seconds. and json data is also changing. How can I follow a path from the application?

You can cache the response by using flutter cache manager Flutter Cache Manager, store it somewhere and use it. For storage you can use shared preferences,sqlite, hive etc there are a lot of options afterwards

you can use stream builder where data is continually updating
https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html
StreamBuilder<int>(
stream: generateNumbers,
builder: (
BuildContext context,
AsyncSnapshot<int> snapshot,
) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.active
|| snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return const Text('Error');
} else if (snapshot.hasData) {
return Text(
snapshot.data.toString(),
style: const TextStyle(color: Colors.red, fontSize: 40)
);
} else {
return const Text('Empty data');
}
} else {
return Text('State: ${snapshot.connectionState}');
}
},
),

Related

how to show the dialog box when session expired or getting response 401 in flutter

My application has a user token and it expires after some time the user login. I got response code 401 and want to navigate to the login page, I am using dio and flutter_secure_storage to save tokens.
else if (e.response?.statusCode == 401) {
//here I want to show the dialog box or navigate to the login page
}
print(getDioException(e));
throw Exception(getDioException(e));
}
If you want to show popup please do like below.
else if (e.response?.statusCode == 401) {
showPopUpAlert();//here you have to call your popup function
}
print(getDioException(e));
throw Exception(getDioException(e));
}
//popup UI implementation
showPopUpAlert() {
showDialog(
context: context,
builder: (_) => AlertDialog(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0))),
content: Builder(builder: (context) {
return StatefulBuilder(builder: (context, stateMaintain) {
return SizedBox(
width: Responsive.isMobile(context)
? Responsive.screenWidth(context) / 1.1
: Responsive.screenWidth(context) / 2,
height: 350,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
AutoSizeText('Popup text',
style:
Theme.of(context).textTheme.headline1),
],
),
);
});
}),
));
}
After some time of research, I found the answer.
we need to create a global key for navigating and after we got the 401 we need to navigate the login page.
define global key final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
pass the value in MaterialApp
navigatorKey: navigatorKey,
and use into your class
navigatorKey.currentState?.pushNamed('/LoginScreen');

FutureBuilder displays that data is null even tho it has value and still proceeds as normal [Flutter]

Hi currently Im working with local json file where I want to use some data from it. I have modeled it accordingly to that json file and when I print it out it works as normal. But when I run it in debug mode it stops at this points saying this :_CastError (type 'Null' is not a subtype of type 'List<UrlCheckModel>' in type cast)
This is my code:
FutureBuilder(
future: readJsonData(),
builder: (context, data) {
if (data.hasError) {
//in case if error found
return Center(child: Text("${data.error}"));
}
List<UrlCheckModel> items = data.data as List<UrlCheckModel>;
Where readJsonData is:
Future<List<UrlCheckModel>> readJsonData() async {
//read json file
final jsondata =
await rootBundle.rootBundle.loadString('jsonfile/malicious.json');
//decode json data as list
final list = json.decode(jsondata) as List<dynamic>;
//map json and initialize using DataModel
return list.map((e) => UrlCheckModel.fromJson(e)).toList();
}
My question is why is this error happening even tho when I run the app it works fine (at brief 1 second time period the error appears) and how can I resolve this. Looking forward to your replies. (If needed I can post the whole code for this page).
Ok so it seams even tho the json file is stored localy and Im not fetching it via API calls it needs to be initialized, so with help of #temp_ one viable solution is like this:
if (data.data == null || data.hasError) {
//in case if error found
return const Center(
child: Text(
'Please wait',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 26,
color: Color.fromRGBO(255, 125, 84, 1),
fontWeight: FontWeight.bold,
),
),
);
}
First off
Before all, you have a error in your widget configuration:
FutureBuilder(
// Do not call the async function directly
future: readJsonData(),
// ...
)
This will result on new Future being instantiated every rebuild, which kills device memory and your app will eventually be killed by the OS.
By the docs:
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
Then you should do something like this:
Future<List<UrlCheckModel>> _future;
void initState() {
super.initState();
_future = readJsonData();
}
Widget build(BuildContext context) {
return FutureBuilder<List<UrlCheckModel>>(
future: _future,
// ..
);
}
Now fixing the actual error
Since the rendering process is sync, which means Flutter can't wait your async process finish to render the first frame of your widget, you should be aware the data will be null in the first snapshot (AsyncSnapshot<T>.nothing()), in the docs:
For a future that completes successfully with data, assuming initialData is null, the builder will be called with either both or only the latter of the following snapshots:
AsyncSnapshot<String>.withData(ConnectionState.waiting, null)
AsyncSnapshot<String>.withData(ConnectionState.done, 'some data')
As you see, your 'data' is actually of type AsyncSnapshot<T>, then:
FutureBuilder<List<UrlCheckModel>>(
future: _future,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Loading();
}
if (snapshot.hasError) {
return HandleYourError();
}
List<UrlCheckModel> items = snapshot.data as List<UrlCheckModel>;
return HappyEnd(items: items);
}
);
See FutureBuilder<T> class reference for details

PopupMenuButton sending Buildcontext to handleclick function

I am pretty new to flutter and I found the code for my PopupMenuButton online and modified it slightly. I want the logout option to run Navigator.pop(context), but I don't understand how to get it get it there.
Code:
PopupMenuButton<String>(
onSelected: handleClick,
itemBuilder: (context) {
return {'Logout', 'Settings'}.map((String choice) {
return PopupMenuItem<String>(
value: choice,
child: Text(choice)
);
}).toList();
}
),
And I want to send it in here:
void handleClick(String value, BuildContext context) {
switch (value) {
case 'Logout':
storage.deleteAll();
//Navigator.pop();
break;
case 'Settings':
break;
}
}
I also tried making Buildcontext a global final late variable, but it seemed to cause a lot of problems.
To be honest, I don't understand how the PopupMenuButton works in regards to calling the funtion, thus me not knowing how to pass another argument
Thanks for the help
Write onSelected like this instead:
onSelected: (value) {
handleClick(value, context);
},

Flutter: How to use Firebase Storage to get new data if JSON file in Firebase Storage updated

I am currently displaying the data by calling the JSON file from Firebase Storage, but I want that instead of download JSON file every single time to show data => I will check if the JSON file from the Firebase Store has changed:
If it changed => download the new JSON file to Local directory and display it.
Otherwise => display the old JSON file in Local directory (This old JSON file will be downloaded when first time App open)
About JSON File
This is JSON link after I upload JSON to Firebase Storage:
https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json?alt=media&token=92e3d416-62dc-4137-93a3-59ade95ac38f
As far as I know, this link is made up of 2 parts:
First part: https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json
Last part: ?alt=media&token= + 2e3d416-62dc-4137-93a3-59ade95ac38f (it is value of String: "downloadTokens" in First part)
In the First part of the link, there is all information about JSON file, and especially I think that value of String "updated" can be used as a condition for the purpose of downloading files or not.
Ex. "updated": "2020-08-04T14:30:10.920Z",
The value of this String updated will change every time I upload a new JSON file with the same name as the old JSON file but the link download will not change.
Steps
So I want to do the following:
Create file to store String "updated" in Local directory (Ex. "updated": null) and where to store the JSON file after download to Local directory
Open App
Check String "updated" in link First Part:
Case A: if value of String "updated" in First Part != value of String "updated" in Local directory =>
Step 1: download JSON file (by link: First part + ?alt=media&token= + downloadTokens) to Local directory (If the old json file already exists, it will be replaced)
Step 2: overwrite value of String "updated" in Local directory by value of String "updated" in Firebase Storage
Step 3: access JSON file in Local directory to display data
Case B: if value of String "updated" in First Part == value of String "updated" in Local directory => do nothing, just access JSON file in Local directory to display data
I know this is a lot of questions for one post, I'm a newbie with code and if I split it up into a few posts then it is very difficult to combine them for me. So I hope the answer with full code, that would be great. Thanks. This is the main file:
import 'package:ask/model/load_data_model.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class LoadDataPage extends StatefulWidget {
#override
_LoadDataPageState createState() => _LoadDataPageState();
}
class DataServices {
static const String url = 'https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json?alt=media&token=92e3d416-62dc-4137-93a3-59ade95ac38f';
static Future<List<Data>> getData() async {
try {
final response = await http.get(url);
if (200 == response.statusCode) {
final List<Data> data = dataFromJson(response.body);
return data;
} else {
return List<Data>();
}
} catch (e) {
return List<Data>();
}
}
}
class _LoadDataPageState extends State<LoadDataPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Load Data')),
body: FutureBuilder(
future: DataServices.getData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<Widget> children;
List<Data> _data = snapshot.data;
if (snapshot.hasData) {
return ListView.builder(
itemCount: _data.length,
itemBuilder: (context, index) {
return Column(
children: [Text(_data[index].data)],
);
},
);
} else {
children = <Widget>[SizedBox(child: CircularProgressIndicator(), width: 60, height: 60), const Padding(padding: EdgeInsets.only(top: 16), child: Text('Loading...'))];
}
return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: children));
}));
}
}
Another Steps
EdwynZN's answer worked great for me, however, I edit the post to add one more case which I think will make load page ASAP, So please help me again:
After open Page => readFile > compareLastUpdate > _lastUpdateDB & _createFile
Case A: The first time the app opens => readFile: false > _lastUpdateDB & _createFile > readFile again
Case B: Not the first time the app opens:
the data is still loaded immediately from the old JSON, at the same time, run in background: compareLastUpdate:
If update times are the same => do nothing
If update times are different => _lastUpdateDB & _createFile
P/S: With this flow, the second time they open the page then new data will be displayed, right? But I wonder that if using StatefulWidget => after the new JSON file is overwritten to the old JSON file => will the phone screen display new data after that?
I would recommend using shared_preferences to save the last updated date as a String
import 'package:shared_preferences/shared_preferences.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:convert';
/// Move them outside of the class as Top Level functions
List<Data> readFile(File file) {
try{
String data = file.readAsStringSync();
return dataFromJson(data);
} catch(e){
print(e.toString());
return List<Data>(); // or return an empty list, up to you
}
}
// No need of encoder now because response body is already a String
void writeFile(Map<String, dynamic> arg) =>
arg['file']?.writeAsStringSync(arg['data'], flush: true);
class DataServices {
DateTime dateApi;
static const String url = 'https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json?alt=media&token=92e3d416-62dc-4137-93a3-59ade95ac38f';
static const String urlUpdate = 'https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json';
Future<List<Data>> getData() async {
bool update = await compareLastUpdate;
if(update) { // that means the update times are the same, so retrieving form json file is better than doing http request
final file = await _createFile();
if(await file.exists()) return await compute(readFile, file);
else return null; //or an empty List
// If it doesn't exists (probably first time running the app)
// then retrieve an empty list, null or check how to fill the list from somewhere else
}
try {
final response = await http.get(url);
final SharedPreferences preferences = await SharedPreferences.getInstance();
if (200 == response.statusCode) {
final String utfData = utf8.decode(response.bodyBytes); //just decode it yourself instead of using response.body which uses [latin1] by default
final List<Data> data = await compute(dataFromJson, utfData);
final file = await _createFile();
Map<String, dynamic> args = {
'file': file,
'data': utfData
//'data': response.body // pass the return body instead of the data
};
await compute(writeFile, args);
await preferences.setString('updateDate', dateApi.toString()); //Save the new date
return data;
} else {
return List<Data>();
}
} catch (e) {
return List<Data>();
}
}
File _createFile() async{
Directory tempDir = await getTemporaryDirectory(); // or check for a cache dir also
return File('${tempDir.path}/Data.json');
}
Future<bool> get compareLastUpdate async{
final dateCache = await _lastUpdateDB;
dateApi = await _lastUpdateApi;
if(dateCache == null) return false;
return dateApi?.isAtSameMomentAs(dateCache) ?? false; // or just isAfter()
// If dateApi is null (an error conection or some throw) just return false or throw an error and
// catch it somewhere else (and give info to the user why it couldn't update)
}
Future<DateTime> get _lastUpdateApi async{
try {
final response = await http.get(urlUpdate);
DateTime dateTime;
if (200 == response.statusCode) {
final data = jsonDecode(response.body));
dateTime = DateTime.tryParse(data['updated'] ?? '');
}
return dateTime;
} catch (e) {
return null;
}
}
Future<DateTime> get _lastUpdateDB async{
final SharedPreferences preferences = await SharedPreferences.getInstance();
return DateTime.tryParse(preferences.getString('updateDate') ?? ''); // Or if it's null use an old date
// The first time the app opens there is no updateDate value, so it returns null, if that
// happens replace it by an old date, one you know your api will be always newer,
// Ex: 1999-08-06 02:07:53.973 Your Api/App didn't even exist back then
// Or just use an empty String so the tryParser returns null
}
}
Then in the widget you just call it the same
class _LoadDataPageState extends State<LoadDataPage> {
final DataServices services = DataServices();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Load Data')),
body: FutureBuilder(
future: services.getData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<Widget> children;
List<Data> _data = snapshot.data;
if (snapshot.hasData) {
return ListView.builder(
itemCount: _data.length,
itemBuilder: (context, index) {
return Column(
children: [Text(_data[index].data)],
);
},
);
} else {
children = <Widget>[SizedBox(child: CircularProgressIndicator(), width: 60, height: 60), const Padding(padding: EdgeInsets.only(top: 16), child: Text('Loading...'))];
}
return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: children));
}));
}
}
Also yu could check Dio package which have some functions over http that let you add parameters to the url

Is there any possible way to open different home page depending on which user is logged in Flutter?

I'm currently developing an App in Flutter and I came across one problem which I'm trying to solve for a while without any success. After someone downloads the app he/she will have to register first in order to use it. User can choose between Driver or Promoter and depending on that, the user will fill up different registration forms. After that process is done (all the data is stored in RealTime database in 2 different folders, driverdata or promoterdata), User will be redirected to the Welcome Page (Authentication Page). After User types in their e-mail and password and click on Log In button, I want to program the path for users to open different Pages (DriverFeed or PromoterFeed).
I had an idea to write a function that will check if an id from user is stored in driverdata or promoterdata, but without any success.
Then I was searching some other solutions on the internet and I came across this: https://www.youtube.com/watch?v=3hj_r_N0qMs but I don't think that this is the good way to solve my problem. I don't want that any User has Moderator claims.
Here is the solution that is working at the moment but not the way I wanted to. I am using ScopedModels and In my connected class / UserModel I wrote:
Future<String> getUserStatus() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String userStatus = prefs.getString('userStatus');
return userStatus;
}
Future<bool> setUserStatus(String userStatus) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('userStatus', userStatus);
return true;
}
Then in the registration Page after the user enters all his data and click on submit button, depending on which user is registering I set their value to the 'driver' or 'promoter' with the following funciton:
setUserStatus('driver');
or
setUserStatus('promoter');
Then in my AuthPage where the user is Loging in, when the user enter his e-mail and password and click on the LogIn button I'm calling log in Function and it looks like this:
Future<void> _submitForm(Function login) async {
if (!_formKey.currentState.validate()) {
return;
}
_formKey.currentState.save();
final Map<String, dynamic> successInformation =
await login(_formData['email'], _formData['password']);
if (successInformation['success']) {
model.getUserStatus().then((a) {
if (a == 'driver') {
Navigator.of(context).pushReplacementNamed('/driverfeed');
} else if (a == 'promoter') {
Navigator.of(context).pushReplacementNamed('/promotions');
} else {
return Container();
}
});
} else {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('An Error Ocurred'),
content: Text(successInformation['message']),
actions: <Widget>[
FlatButton(
child: Text('Okay'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
});
}
}