I'm learning Bloc, but I'm having some trouble to save the data permanently. I Have this TextFormField
where it comes with a name from an API, if I update this name, it should save, and show the update name in the other screens, like the Profile Screen an Editing Profile Screen, like the images below.
enter image description here
ugp5u.png
The problem is, I can save and update the name, but when I restart the app, it comes back to the previously name in the API, and I would like that this updates be saved permanently.
How can I do that?
Here's my state class
class FormNameState extends Equatable {
final String nameForm;
//FormNameState({required this.nameForm, this.formSubmited});
FormNameState({required this.nameForm});
#override
List<Object?> get props => [nameForm];
Map<String, dynamic> toMap() {
return {
'nameForm': nameForm,
};
}
factory FormNameState.fromMap(Map<String, dynamic> map) {
return FormNameState(
nameForm: map['nameForm'],
);
}
String toJson() => json.encode(toMap());
factory FormNameState.fromJson(String source) =>
FormNameState.fromMap(json.decode(source));
}
Event class
abstract class FormNameEvent extends Equatable {
#override
List<Object> get props => [];
}
class UpdateName extends FormNameEvent {
final String nameForm;
UpdateName({required this.nameForm});
}
class FormNameWithTester extends FormNameEvent {}
bloc class
class FormNameBloc extends HydratedBloc<FormNameEvent, FormNameState> {
FormNameBloc(this.loginBloc) : super(FormNameState(nameForm: ''));
final LoginBloc loginBloc;
#override
Stream<FormNameState> mapEventToState(FormNameEvent event) async* {
print(event);
if (event is UpdateName) {
yield* _mapUpdateName(event);
}
/* if (event is FormNameWithTester) {
yield* _mapUpda(event);
} */
//throw UnimplementedError('missingEvent');
}
Stream<FormNameState> _mapUpdateName(UpdateName event) async* {
event.nameForm;
loginBloc.add(FormEditionName(formName: event.nameForm));
print('eventString');
//yield state;
}
#override
FormNameState fromJson(Map<String, dynamic> json) {
return FormNameState.fromJson(json);
}
#override
Map<String, dynamic> toJson(FormNameState state) {
return state.toJson(state);
}
}
You would need to use Shared Preference to store data permanently, On app restart, you can get it back from it.
Related
I saw many examples on internet, but in each case, the data is returning on a listview. I don't want to print in a listview. I want to use data in the app.
This is the way I am addind data on firebase. (I am using a class Info).
void infouser(context) async {
final db = FirebaseFirestore.instance;
final info = Info(yourname, animaName, yourmail);
final uid = await Provider.of(context).auth.getCurrentUID();
await db.collection("userData").doc(uid).collection("info").add(info.toJson());
}
I also tried with set,
createInfo(context) async {
final uid = await Provider.of(context).auth.getCurrentUID();
DocumentReference documentReference =
await FirebaseFirestore.instance.collection('Animal').doc(uid);
Map<String, dynamic> todos = {
'name': yourname,
'animalname' :animalName,
'email' : yourmail,
};
documentReference.set(todos).whenComplete(() {
print( yourname, animalName, yourmail
);
});
}
In both case, I was only able to print data on a Listview. But that is not what I want. I want to have data on a list or a map to be able to use it elsewhere in the app.
Please, I if you have a link(or give me a example of code) where I can see example, it will be appreciate.
thank you.
This is the example of retrieving data as map from firestore:
class GetUserName extends StatelessWidget {
final String documentId;
GetUserName(this.documentId);
#override
Widget build(BuildContext context) {
CollectionReference users = FirebaseFirestore.instance.collection('users');
return FutureBuilder<DocumentSnapshot>(
future: users.doc(documentId).get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data.data();
return Text("Full Name: ${data['full_name']} ${data['last_name']}");
}
return Text("loading");
},
);
}
}
I advise to use https://firebase.flutter.dev/docs/firestore/usage/? documentation when working with Firebase from flutter
I have a plain text on local server that i want to display on my web page using angular. I have my model, service and component.
So I am getting an error at this line >> this.paragraphs = this.announcement.details.split('#');
I tried using ? operator (this.paragraphs = this.announcement?.details.split('#')) but it could not build.
MODEL
export class Announcements
{
public id: number;
public details: string;
public date: Date;
}
SERVICE
getAnnouncementById(id)
{
return this.http.get<Announcements>('http://localhost:49674/api/Announcements/' + id)
.pipe(catchError(this.errorHandler));
}
COMPONENT-.ts
import { Announcements } from '../models/Announcement';
export class ReadMoreComponent implements OnInit
{
public announcementId;
public announcement : Announcements
public paragraphs = [];
constructor(
private route: ActivatedRoute,
private router:Router,
private announcementservice: AnnouncementsService
){}
ngOnInit()
{
this.route.paramMap.subscribe((params:ParamMap) => {
let id = parseInt(params.get('id'));
this.announcementId = id;
this.getAnnouncenentById(id)
//split
this.paragraphs = this.announcement.details.split('#');
})
}
getAnnouncenentById(id){
this.announcementservice.getAnnouncementById(id)
.subscribe(data => this.announcement = data);
}
COMPONENT-.html
<div class="article column full">
<div *ngFor=" let paragraph of paragraphs">
<p>{{paragraph.details}}</p>
</div>
</div>
this.paragraphs = this.announcement.details.split('#'); is called before this.announcement = data so this.annoucement is undefined in that moment.
To be sure that both values already comes form observables you can use combineLatest function or switchMap operator.
Adding ? operator is workaround. Your observable still can call with unexpected order.
e.g.:
this.route.paramMap.pipe(switchMap((params: ParamMap) => {
let id = parseInt(params.get('id'));
this.announcementId = id;
this.getAnnouncenentById(id);
return this.announcementservice.getAnnouncementById(id)
})).subscribe((data) => {
this.announcement = data
this.paragraphs = this.announcement.details.split('#');
});
In that code subscription will start after first observable emits value.
I have a model class named user :
export class User {
constructor(
public UserId?: number,
public Name?: string,
public Password?: string,
public IsActive?: boolean,
public RoleId?: number
) {
}
}
For ng2-select component, I need these properties : text and id.
Now when I set them via
this.userService.getAllUsers().subscribe(
data => this.users = data,
err => this.error(err));
there are no text and id property.
Is there a method where I can set these properties on initialization.
I dont wan't to write everytime a workaround with :
data.forEach(g =>
{
g.text = g.Name;
g.id = g.Id;
});
this.users = data;
Since it seems you are not typing your array users to your User class anyway in the above code snippet, so you could introduce another class and where you handle this assignment in map.
export class UserSelect {
constructor(
public text: string,
public id: number
) {
}
and in your Service:
getAllUsers() {
return this.http......
.map(res => res.json().map(x => new UserSelect(x.Name, x.Id)))
}
and then you just subscribe normally
this.userService.getAllUsers()
.subscribe(data => {
this.users = data;
})
Then you would have an Array of type UserSelect which has the proper properties you need :) Of course you can extend this and include any properties you need, keeping in mind that not knowing to which extent you are using the users array and if you are needing the other properties, or original properties...
I have a typescript base class in my angular project called "Animal" that is extended for several types of animals. Here is some sample code to illustrate. Each extended class will implement it's own "getItems" method and then set "isItemsLoaded" after the promise resolves.
abstract class Animal {
items: Array<...> = [];
isItemsLoaded: boolean = false;
abstract getItems(): ng.IPromise<Array<...>>;
}
class Hippo extends Animal {
getItems() {
//return a promise to get a list of hippos
return this
.getHippos(...)
.then(list, => {
...
this.isItemsLoaded= true;
});
}
}
class Tiger extends Animal {
getItems() {
//return a promise to get a list of tigers
return this
.getTigers(...)
.then(list, => {
...
this.isItemsLoaded= true;
});
}
}
Can I somehow attach some code to run after the getItems resolves (like a finally method) in the Animal base class? I would like to be responsible for setting "isItemsLoaded" in the base. Is something like this possible?
abstract class Animal {
items: Array<...> = [];
isItemsLoaded: boolean = false;
abstract getItems(): ng.IPromise<Array<...>>.AfterResolved(this.isItemsLoaded = true;);
}
You can use template method pattern. Something like this:
// stripped down code for simplicity
abstract class Animal {
isItemsLoaded = false;
protected abstract getItemsForAnimal(): ng.IPromise<Array<...>>;
getItems() {
const items = this.getItemsForAnimal();
this.isItemsLoaded = true;
return items;
}
}
class Hippo extends Animal {
protected getItemsForAnimal() {
// return a promise to get a list of hippos
}
}
As I'm learning Angular 2 I used an observable to fetch some data via an API. Like this:
getPosts() {
return this.http.get(this._postsUrl)
.map(res => <Post[]>res.json())
.catch(this.handleError);
}
My post model looks is this:
export class Post {
constructor(
public title: string,
public content: string,
public img: string = 'test') {
}
The problem I'm facing is that the map operator doesn't do anything with the Post model. For example, I tried setting a default value for the img value but in the view post.img displays nothing. I even changed Post[] with an other model (Message[]) and the behaviour doesn't change. Can anybody explain this behaviour?
I had a similar issue when I wanted to use a computed property in a template.
I found a good solution in this article:
http://chariotsolutions.com/blog/post/angular-2-beta-0-somnambulant-inauguration-lands-small-app-rxjs-typescript/
You create a static method on your model that takes an array of objects and then call that method from the mapping function. In the static method you can then either call the constructor you've already defined or use a copy constructor:
Mapping Method
getPosts() {
return this.http.get(this._postsUrl)
.map(res => Post.fromJSONArray(res.json()))
.catch(this.handleError);
}
Existing Constructor
export class Post {
// Existing constructor.
constructor(public title:string, public content:string, public img:string = 'test') {}
// New static method.
static fromJSONArray(array: Array<Object>): Post[] {
return array.map(obj => new Post(obj['title'], obj['content'], obj['img']));
}
}
Copy Constructor
export class Post {
title:string;
content:string;
img:string;
// Copy constructor.
constructor(obj: Object) {
this.title = obj['title'];
this.content = obj['content'];
this.img = obj['img'] || 'test';
}
// New static method.
static fromJSONArray(array: Array<Object>): Post[] {
return array.map(obj => new Post(obj);
}
}
If you're using an editor that supports code completion, you can change the type of the obj and array parameters to Post:
export class Post {
title:string;
content:string;
img:string;
// Copy constructor.
constructor(obj: Post) {
this.title = obj.title;
this.content = obj.content;
this.img = obj.img || 'test';
}
// New static method.
static fromJSONArray(array: Array<Post>): Post[] {
return array.map(obj => new Post(obj);
}
}
You can use the as keyword to de-serialize the JSON to your object.
The Angular2 docs have a tutorial that walks you through this. However in short...
Model:
export class Hero {
id: number;
name: string;
}
Service:
...
import { Hero } from './hero';
...
get(): Observable<Hero> {
return this.http
.get('/myhero.json')
.map((r: Response) => r.json() as Hero);
}
Component:
get(id: string) {
this.myService.get()
.subscribe(
hero => {
console.log(hero);
},
error => console.log(error)
);
}