hi i'm trying to display the data from http for my project, so far i already connected to the backend and get the response body using http authentication
the response.body from json :
{
"count": 302
}
i wanted to display the "302" to my display, using future and future builder
the model
final String Count;
const Post({required this.Count});
static Post fromJson(Map<String, dynamic> post) => Post(Count: post['count']);
}
trying to map the response body using Map<string,dynamic>
final completer = Completer<List<Post>>();
response.then((value) {
_handleResponse(value);
List<Post> posts = [
Post.fromJson(jsonDecode(value.body) as Map<String, dynamic>)
];
completer.complete(posts);
});
the future builder :
class PostPage extends StatefulWidget {
PostPage({Key? key}) : super(key: key);
#override
State<PostPage> createState() => _PostPageState();
}
class _PostPageState extends State<PostPage> {
final authe count = authe();
#override
Widget build(BuildContext context) {
// getCount();
return
// Text(c);
FutureBuilder<List<Post>>(
future: count.makeGetRequest(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final posts = snapshot.data;
return Text("posts");
} else {
return Center(child: CircularProgressIndicator());
}
},
);
}
}
but when i put it to my main dart all i got is this
https://i.stack.imgur.com/hMjnP.png
did my future builder didn't get my data from my authentication file?
You can just create a dynamic list and assign your response.body to it. It lead to this :
var mylist =[]; myList= jsonDecode(response.body); print(mylist[count]) ;
Related
What I am trying to achieve is a small custom state management solution that I believe is powerful enough to run small and large apps. The core is based on the ValueNotifier and ValueListenable concepts in flutter. The data can be accessed anywhere in the app with out context since I am storing the data like this:
class UserData {
static ValueNotifier<DataLoader<User>> userData =
ValueNotifier(DataLoader<User>());
static Future<User> loadUserData() async {
await Future.delayed(const Duration(seconds: 3));
User user = User();
user.age = 23;
user.family = 'Naoushy';
user.name = 'Anass';
return user;
}
}
So by using UserData.userData you can use the data of the user whenever you want. Everything works fine until I encountered a problem of providing a child to my custom data consumer that rebuilds the widget when there is a new event fired. The DataLoader class looks like this:
enum Status { none, hasError, loading, loaded }
class DataLoader<T> {
Status status = Status.none;
T? data;
Object? error;
bool get hasError => error != null;
bool get hasData => data != null;
}
which is very simple. Now the class for consuming the data and rebuilding looks like this:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:testing/utils/dataLoader/data_loader.dart';
class DataLoaderUI<T> extends StatefulWidget {
final ValueNotifier<DataLoader<T>> valueNotifier;
final Widget noneStatusUI;
final Widget hasErrorUI;
final Widget loadingUI;
final Widget child;
final Future<T> future;
const DataLoaderUI(
{Key? key,
required this.valueNotifier,
this.noneStatusUI = const Text('Data initialization has not started'),
this.hasErrorUI = const Center(child: Text('Unable to fetch data')),
this.loadingUI = const Center(
child: CircularProgressIndicator(),
),
required this.child,
required this.future})
: super(key: key);
#override
State<DataLoaderUI> createState() => _DataLoaderUIState();
}
class _DataLoaderUIState extends State<DataLoaderUI> {
Future startLoading() async {
widget.valueNotifier.value.status = Status.loading;
widget.valueNotifier.notifyListeners();
try {
var data = await widget.future;
widget.valueNotifier.value.data = data;
widget.valueNotifier.value.status = Status.loaded;
widget.valueNotifier.notifyListeners();
} catch (e) {
log('future error', error: e.toString());
widget.valueNotifier.value.error = e;
widget.valueNotifier.value.status = Status.hasError;
widget.valueNotifier.notifyListeners();
}
}
#override
void initState() {
super.initState();
log('init state launched');
if (!widget.valueNotifier.value.hasData) {
log('reloading or first loading');
startLoading();
}
}
//AsyncSnapshot asyncSnapshot;
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<DataLoader>(
valueListenable: widget.valueNotifier,
builder: (context, dataLoader, ui) {
if (dataLoader.status == Status.none) {
return widget.noneStatusUI;
} else if (dataLoader.status == Status.hasError) {
return widget.hasErrorUI;
} else if (dataLoader.status == Status.loading) {
return widget.loadingUI;
} else {
return widget.child;
}
});
}
}
which is also simple yet very effective. since even if the initState function is relaunched if the data is already fetched the Future will not relaunch.
I am using the class like this:
class TabOne extends StatefulWidget {
static Tab tab = const Tab(
icon: Icon(Icons.upload),
);
const TabOne({Key? key}) : super(key: key);
#override
State<TabOne> createState() => _TabOneState();
}
class _TabOneState extends State<TabOne> {
#override
Widget build(BuildContext context) {
return DataLoaderUI<User>(
valueNotifier: UserData.userData,
future: UserData.loadUserData(),
child: Text(UserData.userData.value.data!.name??'No name'));
}
}
The error is in this line:
Text(UserData.userData.value.data!.name??'No name'));
Null check operator used on a null value
Since I am passing the Text widget as an argument with the data inside it. Flutter is trying to pass it but not able to since there is no data yet so its accessing null values. I tried with a normal string and it works perfectly. I looked at the FutureBuilder widget and they use a kind of builder and also the ValueLisnableBuilder has a builder as an arguement. The problem is that I am not capable of creating something like it for my custom solution. How can I just pass the child that I want without having such an error and without moving the ValueLisnable widget into my direct UI widget?
I have found the solution.
Modify the DataLoaderUI class to this:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:testing/utils/dataLoader/data_loader.dart';
class DataLoaderUI<T> extends StatefulWidget {
final ValueNotifier<DataLoader<T>> valueNotifier;
final Widget noneStatusUI;
final Widget hasErrorUI;
final Widget loadingUI;
final Widget Function(T? snapshotData) child;
final Future<T> future;
const DataLoaderUI(
{Key? key,
required this.valueNotifier,
this.noneStatusUI = const Text('Data initialization has not started'),
this.hasErrorUI = const Center(child: Text('Unable to fetch data')),
this.loadingUI = const Center(
child: CircularProgressIndicator(),
),
required this.child,
required this.future})
: super(key: key);
#override
State<DataLoaderUI<T>> createState() => _DataLoaderUIState<T>();
}
class _DataLoaderUIState<T> extends State<DataLoaderUI<T>> {
Future startLoading() async {
widget.valueNotifier.value.status = Status.loading;
widget.valueNotifier.notifyListeners();
try {
var data = await widget.future;
widget.valueNotifier.value.data = data;
widget.valueNotifier.value.status = Status.loaded;
widget.valueNotifier.notifyListeners();
} catch (e) {
log('future error', error: e.toString());
widget.valueNotifier.value.error = e;
widget.valueNotifier.value.status = Status.hasError;
widget.valueNotifier.notifyListeners();
}
}
#override
void initState() {
super.initState();
log('init state launched');
if (!widget.valueNotifier.value.hasData) {
log('reloading or first loading');
startLoading();
}
}
//AsyncSnapshot asyncSnapshot;
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<DataLoader<T>>(
valueListenable: widget.valueNotifier,
builder: (context, dataLoader, ui) {
if (dataLoader.status == Status.none) {
return widget.noneStatusUI;
} else if (dataLoader.status == Status.hasError) {
return widget.hasErrorUI;
} else if (dataLoader.status == Status.loading) {
return widget.loadingUI;
} else {
return widget.child(dataLoader.data);
}
});
}
}
and use it like this:
DataLoaderUI<User>(
valueNotifier: UserData.userData,
future: UserData.loadUserData(),
child: (user) {
return Text(user!.name ?? 'kk');
});
Take a look at my version of the same sort of state management approach here: https://github.com/lukehutch/flutter_reactive_widget
I'm fairly new to Flutter providers. I use Riverpod.
I have a Future provider that provide some data from a JSON file - in the future it will be from a API response.
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/pokemon.dart';
final pokemonProvider = FutureProvider<List<Pokemon>>((ref) async {
var response =
await rootBundle.loadString('assets/mock_data/pokemons.json');
List<dynamic> data = jsonDecode(response);
return List<Pokemon>.from(data.map((i) => Pokemon.fromMap(i)));
});
I subscribe to with ref.watch in ConsumerState widgets, e.g.:
class PokemonsPage extends ConsumerStatefulWidget {
const PokemonsPage({Key? key}) : super(key: key);
#override
ConsumerState<PokemonsPage> createState() => _PokemonsPageState();
}
class _PokemonsPageState extends ConsumerState<PokemonsPage> {
#override
Widget build(BuildContext context) {
final AsyncValue<List<Pokemon>> pokemons =
ref.watch(pokemonProvider);
return pokemons.when(
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
data: (pokemons) {
return Material(
child: ListView.builder(
itemCount: pokemons.length,
itemBuilder: (context, index) {
Pokemon pokemon = pokemons[index];
return ListTile(
title: Text(pokemon.name),
);
},
));
},
);
}
}
But in that case, what is the best practice to write/update data to the JSON file/API?
It seems providers are used for reading/providing data, not updating it, so I'm confused.
Should the same provider pokemonProvider be used for that? If yes, what is the FutureProvider method that should be used and how to call it? If not, what is the best practice?
I am new to riverpod too but I'll try to explain the approach we took.
The examples with FutureProviders calling to apis are a little bit misleading for me, because the provider only offers the content for a single api call, not access to the entire api.
To solve that, we found the Repository Pattern to be very useful. We use the provider to export a class containing the complete api (or a mock one for test purposes), and we control the state (a different object containing the different situations) to manage the responses and updates.
Your example would be something like this:
First we define our state object:
enum PokemonListStatus { none, error, loaded }
class PokemonListState {
final String? error;
final List<Pokemon> pokemons;
final PokemonListStatus status;
const PokemonListState.loaded(this.pokemons)
: error = null,
status = PokemonListStatus.loaded,
super();
const PokemonListState.error(this.error)
: pokemons = const [],
status = PokemonListStatus.error,
super();
const PokemonListState.initial()
: pokemons = const [],
error = null,
status = PokemonListStatus.none,
super();
}
Now our provider and repository class (abstract is optional, but let's take that approach so you can keep the example for testing):
final pokemonRepositoryProvider =
StateNotifierProvider<PokemonRepository, PokemonListState>((ref) {
final pokemonRepository = JsonPokemonRepository(); // Or ApiRepository
pokemonRepository.getAllPokemon();
return pokemonRepository;
});
///
/// Define abstract class. Useful for testing
///
abstract class PokemonRepository extends StateNotifier<PokemonListState> {
PokemonRepository()
: super(const PokemonListState.initial());
Future<void> getAllPokemon();
Future<void> addPokemon(Pokemon pk);
}
And the implementation for each repository:
///
/// Class to manage pokemon api
///
class ApiPokemonRepository extends PokemonRepository {
ApiPokemonRepository() : super();
Future<void> getAllPokemon() async {
try {
// ... calls to API for retrieving pokemon
// updates cached list with recently obtained data and call watchers.
state = PokemonListState.loaded( ... );
} catch (e) {
state = PokemonListState.error(e.toString());
}
}
Future<void> addPokemon(Pokemon pk) async {
try {
// ... calls to API for adding pokemon
// updates cached list and calls providers watching.
state = PokemonListState.loaded([...state.pokemons, pk]);
} catch (e) {
state = PokemonListState.error(e.toString());
}
}
}
and
///
/// Class to manage pokemon local json
///
class JsonPokemonRepository extends PokemonRepository {
JsonPokemonRepository() : super();
Future<void> getAllPokemon() async {
var response =
await rootBundle.loadString('assets/mock_data/pokemons.json');
List<dynamic> data = jsonDecode(response);
// updates cached list with recently obtained data and call watchers.
final pokemons = List<Pokemon>.from(data.map((i) => Pokemon.fromMap(i)));
state = PokemonListState.loaded(pokemons);
}
Future<void> addPokemon(Pokemon pk) async {
// ... and write json to disk for example
// updates cached list and calls providers watching.
state = PokemonListState.loaded([...state.pokemons, pk]);
}
}
Then in build, your widget with a few changes:
class PokemonsPage extends ConsumerStatefulWidget {
const PokemonsPage({Key? key}) : super(key: key);
#override
ConsumerState<PokemonsPage> createState() => _PokemonsPageState();
}
class _PokemonsPageState extends ConsumerState<PokemonsPage> {
#override
Widget build(BuildContext context) {
final statePokemons =
ref.watch(pokemonRepositoryProvider);
if (statePokemons.status == PokemonListStatus.error) {
return Text('Error: ${statePokemons.error}');
} else if (statePokemons.status == PokemonListStatus.none) {
return const CircularProgressIndicator();
} else {
final pokemons = statePokemons.pokemons;
return Material(
child: ListView.builder(
itemCount: pokemons.length,
itemBuilder: (context, index) {
Pokemon pokemon = pokemons[index];
return ListTile(
title: Text(pokemon.name),
);
},
));
}
}
}
Not sure if this is the best approach but it is working for us so far.
you can try it like this:
class Pokemon {
Pokemon(this.name);
final String name;
}
final pokemonProvider =
StateNotifierProvider<PokemonRepository, AsyncValue<List<Pokemon>>>(
(ref) => PokemonRepository(ref.read));
class PokemonRepository extends StateNotifier<AsyncValue<List<Pokemon>>> {
PokemonRepository(this._reader) : super(const AsyncValue.loading()) {
_init();
}
final Reader _reader;
Future<void> _init() async {
final List<Pokemon> pokemons;
try {
pokemons = await getApiPokemons();
} catch (e, s) {
state = AsyncValue.error(e, stackTrace: s);
return;
}
state = AsyncValue.data(pokemons);
}
Future<void> getAllPokemon() async {
state = const AsyncValue.loading();
/// do something...
state = AsyncValue.data(pokemons);
}
Future<void> addPokemon(Pokemon pk) async {}
Future<void> updatePokemon(Pokemon pk) async {}
Future<void> deletePokemon(Pokemon pk) async {}
}
I have json file with the following data in it:
{
"item1": "value1",
"item2": "value2",
"item3": "value3"
}
I also have Items() class in a seperate file which has the method getItems() method which loads the json file:
class Items {
Future<Map<String, dynamic>> getItems() async {
String jsonData =
await rootBundle.loadString('assets/items.json');
Map<String, dynamic> data = jsonDecode(jsonData);
return data;
}
}
I also have a scaffold to show the items with ListView.builder. I first use setState to assign the returned value of Items().getItems() to the field items. I then use the value of the field inside the LisView.builder
class ItemList extends StatefulWidget {
const ItemList({Key? key}) : super(key: key);
#override
_ItemListState createState() => _ItemListState();
}
class _ItemListState extends State<ItemList> {
late String items = '';
setItems() async {
final item = await Items().getItems();
setState(() {
items = jsonEncode(item);
});
}
#override
initState() {
setItems();
}
#override
Widget build(BuildContext context) {
Map<String, dynamic> data = jsonDecode(items);
debugPrint(data.toString());
debugPrint(items);
return Scaffold(
body: ListView.builder(
itemCount: data.keys.length,
itemBuilder: (c, index) {
return ListTile(
title: Text("key " + data.keys.toList()[index]),
subtitle: Text("value " + data.values.toList()[index]),
);
},
),
);
}
}
I am able to show the list of items on the Scaffold() but i still get the error: Unexpected end of input (at character 1)
When i click on the error it highlights on the jsonDecode(items). So something goes wrong there but i don't know what.
Try to simplify your code like this
class _ItemListState extends State<ItemList> {
Map<String, dynamic> data = {};
setItems() async {
await Items().getItems().then((value) => setState(() => data = value));
}
#override
initState() {
setItems();
}
#override
Widget build(BuildContext context) {
debugPrint(data);
//rest of code ...
}
and check if you still get the error.
Here is my stateful widget and url is a property pass it to the widget from parent widget. I don't know where did I go wrong?? I created a future builder widget that has getData() as a future. But the print statement inside was not executed ever. Why is that and it returns me always null value, and this results me a red container appearing on screen and not the table widget.
class TimeTable extends StatefulWidget {
final url;
const TimeTable({Key? key,required this.url}) : super(key: key);
#override
_TimeTableState createState() => _TimeTableState();
}
class _TimeTableState extends State<TimeTable> {
Future<List<Train>> getData() async{
final list = await TrainClient(url: widget.url).getName();
print("this line not executed");
return list;
}
#override
Widget build(BuildContext context) {
return Scaffold(body: FutureBuilder(
future: getData(),
builder: (context,projectSnap){
if(projectSnap.connectionState == ConnectionState.none ||
projectSnap.data == null) {
return Container(color: Colors.red,);
}
return buildDataTable(trains: projectSnap.data);
}));
}
}
getData is a future method and it returns a list, The list gets printed when I call that object Train Client. I had my print statement inside TrainClient class to check whether the list is created successfully.
Here is the code of TrainClient
class TrainClient {
final String url;
TrainClient({required this.url});
Future<List<Train>> getName() async {
final uri = Uri.parse(url);
final response = await get(uri);
if (response.statusCode == 200) {
print("ulla");
final data = json.decode(response.body);
final result = data["RESULTS"]["directTrains"]["trainsList"];
final list = result.map((json) => Train.fromJson(json));
print(list);
return list;
}else{
throw Exception();
}
}
}
The TrainClient class has no error since it printed the list successfully as shown below
(Instance of 'Train', Instance of 'Train', Instance of 'Train', ..., Instance of 'Train', Instance of 'Train')
You should always obtain future earlier (in initState/didChangeDependencies).
Each time your build is executed, new future is created. So it never finishes, if your widget rebuilds often.
late final _dataFuture = getData();
...
FutureBuilder(
future: _dataFuture,
builder: (context,projectSnap){
...
}
);
I'm trying to transport function result of json process from class to another class.
This is the json I rendered:
[
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat",
"body": "quia et suscipit\nsuscipit"
},
{
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi"
}
]
Result of method is showing when I tried to print inside that method, but not printed on destination class. These is my codes:
Source class:
class DataNews {
final String id;
final String title;
final String body;
DataNews({this.id, this.title, this.body});
factory DataNews.fromJson(json) {
return DataNews(id: json['id'].toString(), title: json['title'], body: json['body']);
}
}
getData() async {
final response = await http.get('https://jsonplaceholder.typicode.com/posts');
List data = json.decode(response.body);
final items = (data).map((i) => new DataNews.fromJson(i));
print('Satu');
print(items.elementAt(1).id);
print(items.elementAt(1).title);
print(items.elementAt(1).body);
print('Dua');
return items;
}
Destionation class:
class News extends StatefulWidget {
#override
_NewsState createState() => _NewsState(post: getData());
}
class _NewsState extends State<News> {
Iterable<dynamic> post;
_NewsState({Key key, this.post});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Text(post.elementAt(0).title),
),
);
}
}
I want to show one of the result on Container. How to do that?
First, your method getData() doesn't return an Iterable, it returns a Future<Iterable>, but it would be better if it returned a Future<List>, instead. Please, annotate the types you're using, it'll make your life much easier.
Future<List<DataNews>> getData() async {
final response = await http.get('https://jsonplaceholder.typicode.com/posts');
List data = json.decode(response.body);
List<DataNews> items = data.map((i) => DataNews.fromJson(i)).toList();
return items;
}
Second, you should not pass any data to _NewsState. Either the data comes from the parent Widget and gets stored in News or you fetch the data inside _NewsState itself. FutureBuilder can help you to display the content that comes from a Future.
class News extends StatefulWidget {
#override
_NewsState createState() => _NewsState();
}
class _NewsState extends State<News> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: getData(), // Fetch the data
builder: (_, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
// If your List got fetched, them show each DataNews using a ListView
List<DataNews> newsList = snapshot.data;
return ListView.builder(
itemCount: newsList.length,
itemBuilder: (_, int index) {
return Container(
child: Text(newsList[index].title),
);
},
);
} else {
// If you have no data, show a progress indicator
return Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}