How can I parse json data from websocket server in flutter? - json

The WebSocket server is supposed to shoot json data as soon as it goes up, and I want to create a StreamBuilder that updates the data as it shoots me FutureBuilder has received async and wait http.get, and how do I make a Stream for my stream builder?
Here is the futurebuilder when I receive information from http server!(for all information, no realtime)
I want to receive information like this by streambuilder from websocket server for realtime!
class BallInformationWidget extends StatefulWidget {
#override
BallInformationWidgetState createState() => BallInformationWidgetState();
}
class BallInformationWidgetState extends State<BallInformationWidget> {
#override
Widget build(BuildContext context) {
return FutureBuilder<List<BallInformation>>(
future: getHttpBallInfo(urlPrefix),
builder: (context, snapshot) {
print('http data : ${snapshot.data}');
print('http length : ${snapshot.data.length}\n');
print('http type : ${snapshot.data[0].id.runtimeType}\n');
BallInformation ballInfo = snapshot.data[0]; // 공하나 객체 선언 후에 for 문?
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0),
child: Text(snapshot.hasData ? '${ballInfo.id}' : 'Loading...'),
);
},
);
}
}
class BallInformation {
int id;
double speed;
String result;
Map trajectoryParameter;
BallInformation(this.id, this.speed, this.result, this.trajectoryParameter);
BallInformation.fromJson(Map<String, dynamic> data) {
this.id = data['id'];
this.speed = data['speed'];
this.result = data['result'];
this.trajectoryParameter = data['trajectory_parameter'];
}
}
Future<List<BallInformation>> getHttpBallInfo(String url) async {
http.Response response = await http.get(url);
String responseBody = response.body;
var dataS = json.decode(responseBody);
List<BallInformation> ballInformation = [];
// If I receive json(only one information
ballInformation.add(BallInformation.fromJson(dataS));
// If I receive json(List, more than 1 information)
// for (var data in dataS){
// ballInformation.add(BallInformation.fromJson(data));
// }
return ballInformation;
}

The standart method for this is json.decode(snapshot.data). This will return a map of your json data in the request.

Related

What push data payload structure I need for this handler? (Flutter)

I am using FLutterFlow to build an app and my coding level is beginner. I need to create a valid push data payload structure (JSON) at backend side to pass to app two parameters:
app page (tab) name that should be opened when user taps on push (is called "initialPageName" in code below)
push unique id assigned by backend (is called "pushGUID" in code below)
Below is push handler code that is under the hood of FlutterFlow web app builder, I can not change it. I know it works, because when I use FlutterFlow web push sender and fill in "initial page" and "push GUID" everything works fine. But when I send push from my backend using FCM HTTP API I get only notification itself, data payload with "push GUID" is not handled correctly.
import 'dart:async';
import 'dart:convert';
import 'serialization_util.dart';
import '../backend.dart';
import '../../flutter_flow/flutter_flow_theme.dart';
import '../../flutter_flow/flutter_flow_util.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import '../../index.dart';
import '../../main.dart';
final _handledMessageIds = <String?>{};
class PushNotificationsHandler extends StatefulWidget {
const PushNotificationsHandler({Key? key, required this.child})
: super(key: key);
final Widget child;
#override
_PushNotificationsHandlerState createState() =>
_PushNotificationsHandlerState();
}
class _PushNotificationsHandlerState extends State<PushNotificationsHandler> {
bool _loading = false;
Future handleOpenedPushNotification() async {
if (isWeb) {
return;
}
final notification = await FirebaseMessaging.instance.getInitialMessage();
if (notification != null) {
await _handlePushNotification(notification);
}
FirebaseMessaging.onMessageOpenedApp.listen(_handlePushNotification);
}
Future _handlePushNotification(RemoteMessage message) async {
if (_handledMessageIds.contains(message.messageId)) {
return;
}
_handledMessageIds.add(message.messageId);
if (mounted) {
setState(() => _loading = true);
}
try {
final initialPageName = message.data['initialPageName'] as String;
final initialParameterData = getInitialParameterData(message.data);
final parametersBuilder = parametersBuilderMap[initialPageName];
if (parametersBuilder != null) {
final parameterData = await parametersBuilder(initialParameterData);
context.pushNamed(
initialPageName,
params: parameterData.params,
extra: parameterData.extra,
);
}
} catch (e) {
print('Error: $e');
} finally {
if (mounted) {
setState(() => _loading = false);
}
}
}
#override
void initState() {
super.initState();
handleOpenedPushNotification();
}
#override
Widget build(BuildContext context) => _loading
? Container(
color: Colors.white,
child: Image.asset(
'assets/images/logo.png',
fit: BoxFit.contain,
),
)
: widget.child;
}
class ParameterData {
const ParameterData(
{this.requiredParams = const {}, this.allParams = const {}});
final Map<String, String?> requiredParams;
final Map<String, dynamic> allParams;
Map<String, String> get params => Map.fromEntries(
requiredParams.entries
.where((e) => e.value != null)
.map((e) => MapEntry(e.key, e.value!)),
);
Map<String, dynamic> get extra => Map.fromEntries(
allParams.entries.where((e) => e.value != null),
);
static Future<ParameterData> Function(Map<String, dynamic>) none() =>
(data) async => ParameterData();
}
final parametersBuilderMap =
<String, Future<ParameterData> Function(Map<String, dynamic>)>{
'PhoneAuth': ParameterData.none(),
'smsVerify': (data) async => ParameterData(
allParams: {
'phoneNumber': getParameter<String>(data, 'phoneNumber'),
},
),
'MainPage': (data) async => ParameterData(
allParams: {
'pushGUID': getParameter<String>(data, 'pushGUID'),
},
),
'Campaign': ParameterData.none(),
'LoyaltyPoints': ParameterData.none(),
'OnTheMap': ParameterData.none(),
'NewCollections': ParameterData.none(),
'Notifications': ParameterData.none(),
};
Map<String, dynamic> getInitialParameterData(Map<String, dynamic> data) {
try {
final parameterDataStr = data['parameterData'];
if (parameterDataStr == null ||
parameterDataStr is! String ||
parameterDataStr.isEmpty) {
return {};
}
return jsonDecode(parameterDataStr) as Map<String, dynamic>;
} catch (e) {
print('Error parsing parameter data: $e');
return {};
}
}
My guess looking at the code above is that I should use nested JSON like this:
{
"message":{
"notification":{
"body" : "my body",
"title" : "my title",
},
"data" : {
"initialPage":"mainPage",
"params":[
{"pushGUID":"1231231231"}
]
}
}
}
But as I mentioned my Futter code level is beginner, so please have a look at the handler code and correct my JSON structure, thanks for any advice.

The argument type 'Object?' can't be assigned to the parameter type 'Map<dynamic, dynamic>

I want to get a category section from Firebase Firestore so I used this class
class CategoryModel {
late String name, image;
CategoryModel({
required this.name,
required this.image,
});
CategoryModel.fromJson(Map<dynamic, dynamic> map) {
if (map == null) {
return;
}
name = map['name'];
image = map['image'];
}
toJson() {
return {
'name': name,
'image': image,
};
}
}
and then I created this with gets
class HomeViewModel extends GetxController {
ValueNotifier<bool> get loading => _loading;
ValueNotifier<bool> _loading = ValueNotifier(false);
List<CategoryModel> get categoryModel => _categoryModel;
List<CategoryModel> _categoryModel = [];
HomeViewModel() {
getCategory();
}
getCategory() async {
_loading.value = true;
await HomeService().getCategory().then((value) {
for (int i = 0; i < value.length; i++) {
_categoryModel.add(
CategoryModel.fromJson(
value[i].data(),
),
);
_loading.value = false;
}
update();
});
}
}
but when I try to get categories from Firestore with the function getCategory() this error comes
Error in vs code
Error in Problems scetion
Try it this way and tell me if it's fixed.
CategoryModel.fromJson(
value[i].data() as Map<String,dynamic>,
),
You can read more about Cloud Firestore 2.0 here.
The basic idea is that wherever you are trying to read the data from a DocumentSnapshot, you need to cast the Object? to Map<String, dynamic>.
You can do this in a few ways.
Cast the Object? to Map<String, dynamic> in your toJson method
CategoryModel.fromJson(
value[i].data() as Map<String,dynamic>,
),
Specify the type in your constructors
DocumentReference<Map<String, dynamic>> documentReference;
Specify the type in your StreamBuilder
StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: model.testSnap,
builder: (context, snapshot) {
return Container();
},
)

How to use websockets for my JSON streambuilder?

I have set up the streambuilder and stream async functions,but i do not know how to use it properly as it needs websockets. How can i use websockets in these case to fetch my Json data from my server?
Stream<List<Post>> FetchPosts(http.Client client) async* {
final response = await http.get("$SERVER_IP/api/articles/?format=json");
final parsed = jsonDecode(utf8.decode(response.bodyBytes)).cast<Map<String, dynamic>>();
yield parsed.map<Post>((json) => Post.fromJSON(json)).toList();
}
StreamBuilder<List<Post>>(
stream: FetchPosts(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData ? PostsList(posts: snapshot.data)
:
Center(child: CircularProgressIndicator(backgroundColor: Colors.pink,valueColor: new AlwaysStoppedAnimation<Color>(Colors.pinkAccent)));
},
),
Class
class Post {
final String id;
final String author;
final String caption;
Post({this.id, this.author,this.caption});
factory Post.fromJSON(Map<String, dynamic> jsonMapPost) {
return Post(
id: jsonMapPost['id'] as String,
caption: jsonMapPost['caption'] as String,
author: jsonMapPost['author'] as String,);}}
Json data
[{"id":"a4d64194-3d81-4a4e-b7e6-6fce36fea793","author":"a94d4433-ffaf-4af1-aa8d-67ac75bf2e53","caption":"this is caption",]}
You can use web_socket_channel package that provides StreamChannel wrappers for WebSocket connections.
With it you fetchPosts function can turn into something like that:
Stream<List<Post>> fetchPosts() {
final channel = IOWebSocketChannel.connect(Uri.parse("$SERVER_IP/api/articles/?format=json"));
return channel.stream.map((r) => parse(r));
}
If your websocket server emits message in this format:
[
{
"id": "a4d64194-3d81-4a4e-b7e6-6fce36fea793",
"author": "a94d4433-ffaf-4af1-aa8d-67ac75bf2e53",
"caption": "this is caption"
}
]
then your parse function can look like that:
List<Post> parse(String message) {
final json = jsonDecode(message) as List<dynamic>;
return json.map((j) => Post.fromJSON(j as Map<String, dynamic>)).toList();
}

FormatException: Unexpected character (at character 1) <!DOCTYPE html> Flutter Google Sheets

I'm getting this weird response while using http in Flutter/Dart. Similar code is working fine with other APIs endpoints but not this. Although the link has jSON data not any other format.
I have already check the following links and NONE of them is related to mine:
formatexception (formatexception: unexpected character (at character 1) json
Exception: FormatException: Unexpected character (at character 1)
Exception
Explanation
The exception I'm getting is similar. And I know that it is return HTML instead of jSON. But my link is NOT an HTML. Its a array of jSON Objects
Code
Apps Script Code for Google Sheets
function doGet(request) {
var sheet = SpreadsheetApp.openById("1CBPpqvdUpPaYMjxpX_9-ywMsErT06fD6AfzASWBFnnk");
var values = sheet.getActiveSheet().getDataRange().getValues();
var data = [];
for (var i = values.length - 1; i > 0; i--) {
var row = values[i];
var story = {};
story['Latitude'] = row[5];
story['Longitude'] = row[6];
console.log(story['Longitude'], story['Longitude']);
data.push(story);
}
return ContentService
.createTextOutput(JSON.stringify(data))
.setMimeType(ContentService.MimeType.JSON);
}
Model Class
class StoryList {
final List<Story> stories;
StoryList({this.stories});
factory StoryList.fromJson(List<dynamic> parsedJson) {
List<Story> story = new List<Story>();
story = parsedJson.map((i) => Story.fromJSON(i)).toList();
return new StoryList(stories: story);
}
}
class Story {
final String longitude;
final String latitude;
Story({this.latitude, this.longitude});
factory Story.fromJSON(Map<String, dynamic> json) {
return Story(
longitude: json['Longitude'],
latitude: json['Latitude'],
);
}
}
Controller Class
You won't be able to access the link its restricted for organization use only
class StoryController {
Future<StoryList> getCountryData() async {
String url =
'https://script.google.com/a/macros/storius.app/s/AKfycbyzx4kIlVdTC9QVVBovVfWMDFWdk9noomDJV4XcyDApnsMYTe68u0mL/exec';
final response = await http.get(url);
if (response.statusCode == 200) {
final jsonRes = json.decode(response.body);
return StoryList.fromJson(jsonRes);
} else {
throw Exception("Failed due to Network Error");
}
}
}
View Class
For the time being I'm only getting Longitude for testing purposes.
class HomeView extends StatefulWidget {
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: StoryController().getCountryData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data.stories[index].longitude),
);
});
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
));
}
}
As per our Discord discussion, when you printed response.body at my suggestion you see that you are getting an html response from the url, not json as you expected.
The problem is with authentication on the linked website rather than with the Flutter/Dart code. You need to resolve that authentication issue.
It is recommended that when testing new code that accesses a database or a url to always print out, or use a debugger to view, the returned data. Never assume you are getting what you expect.

Flutter - Delaying json requests to do once per time

i'm trying to do a Dashboard with multiples jsons requests, but i want that request work one per one, like when finish first request start the second, when finish second start the third when finish third start the N.
my list code:
new CustomScrollView(
cacheExtent: height * 6,
slivers: [
new SliverList(
delegate: new SliverChildListDelegate(
[
new RelatorioVendPeriodoAPeriodo(),
new RelatorioMensals(),
new RelatorioDiasDaSemanas(),
new RelatorioVendasTotalidasPorPeriodo(),
new RelatorioDasVendasTotsProdutos(),
]
)
)
]
),
this new classes calls, returns for me request. Anyone knows how to delay it?
First, the parent widget should return a progress bar when the necessary data is not available.
A service will be called an initState to fetch data from the backend. when data is ready setState() will be called to redraw the widget.
Look at this example:
class _TestWidgetState extends State<TestWidget> {
var data;
#override
void initState() {
data = NetworkService.getData().then((data) {
setState(() {
this.data = data;
});
});
}
#override
Widget build(BuildContext context) {
if (data == null) {
return CircularProgressIndicator();
} else {
return
new CustomScrollView(
cacheExtent: height * 6,
slivers: [
new SliverList(
delegate: new SliverChildListDelegate(
[
new RelatorioVendPeriodoAPeriodo(data: data),
new RelatorioMensals(data: data),
new RelatorioDiasDaSemanas(data: data),
new RelatorioVendasTotalidasPorPeriodo(data: data),
new RelatorioDasVendasTotsProdutos(data: data),
]
)
)
]
);
}
}
}
class NetworkService {
final JsonDecoder _decoder = new JsonDecoder();
static String data1;
static String data2;
static getData() async {
if (data1 == null || data2 == null) {
await fetchFromServer();
}
return {'data1': data1, 'data2': data2};
}
static fetchFromServer() async {
data1 = (await http.get('url')).body;
data2 = (await http.get('url')).body;
}
}
Future.delayed(const Duration(milliseconds: 500), () {
print(" This line is executed after 5 seconds");
});