I am trying to create a search function from firebase
and i am using getx for the state management
I counter this weird problem, when I try to use the function directly it work, but when I use the firebase function it does not.
I will explain more on the code snipit.
this is my search textForm
TextFormField(
onChanged: (value) => database.searchForId(value),
decoration: InputDecoration(
focusColor: Colors.lightGreenAccent,
prefixIcon: Icon(
Icons.search,
color: Colors.lightGreenAccent,
),
labelText: 'Search...',
labelStyle: TextStyle(color: Colors.lightGreenAccent),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: Colors.lightGreenAccent),
),
)),
as you can see I am using on change value
database code for search
searchForId(searchText) {
FirebaseFirestore.instance
.collection('inventory')
.where('id', isGreaterThanOrEqualTo: searchText)
.get()
.then((snapshot) {
var data = snapshot.docs[0];
if (data['id'] != searchText) {
return;
} else {
searchController.changeId(data['id']);
searchController.changeName(data['name']);
searchController.changeQuantity(data['quantity']);
searchController.changeCost(data['cost']);
searchController.changeMyPrice(data['myPrice']);
}
});
}
and the controller
class SearchController extends GetxController {
var id = 'No info'.obs;
var name = 'No info'.obs;
var quantity = 'No info'.obs;
var cost = 'No info'.obs;
var myPrice = 'No info'.obs;
changeId(_id) {
id(_id);
print(id);
}
the print show me that the value is changed, but never update it on my UI
Obx(() => Text(searchController.id.value))
so i trid to call the controller dirctly (not using database file) and it work just fine, and UI updated
any idea why ?
the weird is the print show me the function get called and working fine ( i mean when i use database file as well ) but it never update the UI
I think u forgot to keep
SearchController searchcontroller = Get.put(SearchController());
if you initialized it before just find that controller using
SearchController searchcontroller = Get.find();
var id = 'No info'.obs;
searchController.changeId(data['id']);
changeId(_id) {
id(_id);
print(id);
}
In order to update your text in UI, you can either update your code
changeId(_id) {
id(_id);
print(id);
update(); // Calling update manually. update() is a function in GetxController to trigger the observable change.
}
or
changeId(String _id) {
id.value = _id;
}
Related
Is there a way to catch only the final search to send in Firebase Analytics event?
_searchPressed() {
final FirebaseAnalytics analytics = FirebaseAnalytics.instance;
setState(() {
if (this._searchIcon.icon == Icons.search) {
this._searchIcon = new Icon(Icons.close);
this._appBarTitle = TextField(
decoration: new InputDecoration(
prefixIcon: new Icon(Icons.search),
),
onChanged: (text) {
text = text.toLowerCase();
setState(() {
_itemForDisplay = _item.where((item) {
var itemTitle = item.code.toLowerCase();
return itemTitle.contains(text);
}).toList();
if (text.length > 6) {
analytics.logEvent(
name: text.replaceAll(' ', ''),
parameters: {'term': text.replaceAll(' ', ''), 'vaule': 1});
//space not allowed in firebase
}
});
},
);
} else {
this._searchIcon = new Icon(Icons.search);
this._appBarTitle = new Text( 'Search bar' );
analytics.logEvent(
name: 'search_done',
parameters: {'term': "close", 'vaule': 1});
_itemForDisplay = _item;
}
});
}
A satisfactory solution is to capture the search when closing search is selected. The search is done simultaneously so there is no information when the search is completed. The only information about the end of the search is when you touch the close icon. Another option would be the longest search text.
I made this notes app which uses firestore to save data and it is working fine, no problem whatsoever.
Now I am trying to implement a search filter on notes and I can't find the right method to do it.
Here is my code that renders the notes on the screen
here is the reference image
List<Widget> _buildNotesView(
BuildContext context, NoteFilter filter, List<Note> notes) {
if (notes?.isNotEmpty != true) {
return [_buildBlankView(filter.noteState)];
}
final asGrid = filter.noteState == NoteState.deleted || notesView;
final factory = asGrid ? NotesGrid.create : NotesList.create;
final showPinned = filter.noteState == NoteState.unspecified;
if (!showPinned) {
return [
factory(notes: notes, onTap: _onNoteTap),
];
}
final partition = _partitionNotes(notes);
final hasPinned = partition.item1.isNotEmpty;
final hasUnpinned = partition.item2.isNotEmpty;
final _buildLabel = (String label, [double top = 26]) => SliverToBoxAdapter(
child: Container(
padding:
EdgeInsetsDirectional.only(start: 26, bottom: 25, top: top),
child: Text(
label,
style: TextStyle(
fontFamily: selectedFont,
color: kHintTextColorLight,
fontWeight: FontWeights.medium,
fontSize: 12,
),
),
),
);
//TODO
if (searchController.text.isNotEmpty) {
notes.forEach((note) {
if (note.title
.toLowerCase()
.contains(searchController.text.toLowerCase()) ||
note.content
.toLowerCase()
.contains(searchController.text.toLowerCase())) ;
//Do something here?
});
}
return [
if (hasPinned) _buildLabel('PINNED', 0),
if (hasPinned) factory(notes: partition.item1, onTap: _onNoteTap),
if (hasPinned && hasUnpinned) _buildLabel('OTHERS'),
factory(notes: partition.item2, onTap: _onNoteTap),
];
}
Any help would be great. I'm open to learning.
I am doing a project where I have to keep an favorite icon and a selected fvrt list... Now using sqlflite .. I have done it.. when the user presses the favorite border icon it get changed to red color and the data saves in the favorite list.. when user pressses again in the same button .. the data gets delated from the list and the favorite button change to ist default color... but what i am not able to do is.. the favorite button is default false.. so even if the data is collected in the fvrt list .. all the fvrt button shows _fvrt default favorite btn when i start the app ...
i was wondering how can i check the data in the initState() , if the data already exit in database it fvrt btn will remain red..
here's a little code of the conditon that i haved used .
Widget _buildRow(String pair) {
final bool alreadySaved = _saved.contains(pair);
print("Already saved $alreadySaved");
print(pair);
return IconButton(
icon: new Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color:alreadySaved? Colors.red : Colors.white,
),onPressed: (){
setState(() {
if (alreadySaved) {
_saved.remove(pair);
_deleteEmployee(pair);
} else {
_saved.add(pair);
_insert(pair);
}
});
},
);
}
Reading data from your database is an async function - it takes some time. What you can do, is to create a loading state, and show a loading indicator, until the async function finishes.
import 'package:flutter/material.dart';
class MyClass extends StatefulWidget {
#override
_MyClassState createState() => _MyClassState();
}
class _MyClassState extends State<MyClass> {
bool isLoading = false;
List _saved = [];
#override
void initState() {
// Note that you cannot use `async await` in initState
isLoading = true;
_readFromDataBase().then((savedStuff) {
_saved = savedStuff;
isLoading = false;
});
super.initState();
}
#override
Widget build(BuildContext context) {
return !isLoading ? _buildRow("myPair") : CircularProgressIndicator();
}
Widget _buildRow(String pair) {
final bool alreadySaved = _saved.contains(pair);
print("Already saved $alreadySaved");
print(pair);
return IconButton(
icon: new Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color:alreadySaved? Colors.red : Colors.white,
),onPressed: (){
setState(() {
if (alreadySaved) {
_saved.remove(pair);
_deleteEmployee(pair);
} else {
_saved.add(pair);
_insert(pair);
}
});
},
);
}
}
Alternatively you can check the FutureBuilder Widget. Here is the official documentation: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
I'm using a drop-down list (DropDown), whose elements are obtained from Firebase. The form works right, however when the internet connection is lost the Firebase Offline Persistence property doesn't work and the CircularProgressIndicator stays active. Reading some responses such as Using Offline Persistence in Firestore in a Flutter App, it is indicated that awaits should not be handled, however it is not clear to me how to achieve it:
class EstanqueAlimentarPage extends StatefulWidget {
#override
_EstanqueAlimentarPageState createState() => _EstanqueAlimentarPageState();
}
class _EstanqueAlimentarPageState extends State<EstanqueAlimentarPage> {
final formKey = GlobalKey<FormState>();
AlimentoBloc alimentoBloc = new AlimentoBloc();
AlimentoModel _alimento = new AlimentoModel();
AlimentarModel alimentar = new AlimentarModel();
List<AlimentoModel> _alimentoList;
bool _alimentoDisponible = true;
#override
void dispose() {
alimentoBloc.dispose();
super.dispose();
}
#override
void initState() {
_obtenerListaAlimentoUnaVez();
super.initState();
}
Future<void> _obtenerListaAlimentoUnaVez() async {
_alimentoList = await alimentoBloc.cargarAlimento(idEmpresa); // Await that I want to eliminate
if (_alimentoList.length > 0) { // Here appears a BAD STATE error when the internet connection goes from off to on
_alimento = _alimentoList[0];
_alimentoDisponible = true;
} else {
_alimentoDisponible = false;
}
_cargando = false;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(
children: <Widget> [
_crearTipoAlimento(_alimentoList),
SizedBox(height: 8.0),
_crearComentarios(),
]
)
),
_crearBoton('Guardar'),
}
Widget _crearTipoAlimento(List<AlimentoModel> lista) {
return Container(
decoration: _cajaBlanca,
child:
!_cargando // If it isn't loading, Dropdown must be displayed
? DropdownButtonFormField<AlimentoModel>(
decoration: InputDecoration(
labelText: 'Nombre del Alimento',
contentPadding: EdgeInsets.only(top:5.0),
prefixIcon: Icon(FontAwesomeIcons.boxOpen, color: Theme.of(context).primaryColor,),
border: InputBorder.none,
),
value: _alimento,
items: lista.map((AlimentoModel value) {
return DropdownMenuItem<AlimentoModel>(
child: Text(value.nombre),
value: value,
);
}).toList(),
onChanged: (_alimentoDisponible) ? (AlimentoModel _alimentoSeleccionado) {
print(_alimentoSeleccionado.nombre);
_alimento = _alimentoSeleccionado;
setState(() {});
} : null,
disabledHint: Text('No hay Alimento en Bodega'),
onSaved: (value) {
alimentar.idAlimento = _alimento.idAlimento;
alimentar.nombreAlimento = _alimento.nombreRef;
}
)
: Center (child: CircularProgressIndicator(strokeWidth: 1.0,))
);
}
Widget _crearComentarios() {
return TextFormField(
// -- DESIGN OTHER FIELDS -- //
onSaved: (value) {
alimentar.comentarios = value;
}
),
);
}
Widget _crearBoton(String texto) {
return RaisedButton(
// -- DESIGN -- //
onPressed: (_guardando) ? null : _submit,
),
);
}
void _submit() {
// CODE TO WRITE FORM IN FIREBASE
}
}
The function code from my BLOC is:
Future<List<AlimentoModel>> cargarAlimento(String idEmpresa, [String filtro]) async {
final alimento = await _alimentoProvider.cargarAlimento(idEmpresa, filtro); //It's one await more
_alimentoController.sink.add(alimento);
return alimento;
}
And the Query from PROVIDER is:
Future<List<AlimentoModel>> cargarAlimento(String idEmpresa, [String filtro]) async {
Query resp;
final List<AlimentoModel> alimento = new List();
resp = db.child('empresas').child(idEmpresa).child('bodega/1').child('alimento')
.orderByChild('cantidad').startAt(0.000001);
return resp.once().then((snapshot) {
if (snapshot.value == null) return [];
if (snapshot.value['error'] != null) return [];
snapshot.value.forEach((id, alim){
final temp = AlimentoModel.fromJson(Map<String,dynamic>.from(alim));
temp.idAlimento = id;
alimento.add(temp);
});
return alimento;
});
When using Firebase offline, you omit the await only on things that change the server (e.g., creating or updating a record). So you won't wait for the server to say "yes I wrote it", you assume that it's written.
In your case, however, you are not writing data, you are reading data. You will have to keep await in your example. The way you load your data has orderByChild and startAt, maybe those are preventing offline loading. Normally, you get it if it's already in the cache: https://firebase.google.com/docs/firestore/manage-data/enable-offline#get_offline_data
You mention a BAD STATE error, maybe if you provide that, we may be able to pinpoint the issue a bit better.
How can I activate/deactivate a button based on the content of a TextFormField?
I want to make sure a user can only press Button X if the TextFormField has 10 entered numbers (if it's a mobile number).
Thanks!
The state of the widget can be updated in the Form's onChanged callback which is called whenever any of the form fields' value changes. There you can use a form key to validate the form and set a flag to enable/disable the button. This solution allows you to scale to disable buttons in forms with multiple fields. For example,
/// Key used to reference the form.
final _formKey = GlobalKey<FormState>();
...
Form(
key: _formKey,
onChanged: () => setState(() => _enableBtn = _formKey.currentState.validate()),
child: ListView(
children: <Widget>[
TextFormField(
validator: (value) => value.length < 10 ?
'Number must be at least 10 digits' : // return an error message
null,
...
),
],
),
)
...
FlatButton(
onPressed: _enableBtn ?
() => _doSomething() :
null, // setting onPressed to null disables the button.
...
A simple way would be to set the autovalidate property of our TextFormField to true. This will automatically detect for changes on our TextFormField widget. We can then try to check if our TextFormField's value has a String length of 10 characters on the validator property . After that we can call setState to enable or disable our button (I use FlatButton in this example).
bool _btnEnabled = false;
...
#override
Widget build(BuildContext context){
...
TextFormField(
...
autovalidate: true,
validator: (String txt){
if (txt.length == 10){
setState((){
_btnEnabled = true;
});
} else {
setState((){
_btnEnabled = false;
});
}
}
...
FlatButton(
onPressed: _btnEnabled == true ? yourCallback : null,
child: ...
you can use Flutter Reactive Forms. It's a model-driven approach to handling Forms inputs and validations, heavily inspired in Angular's Reactive Forms.
It's a very simple to use libray and in the documentation there is a section that explains how to Enable/Disable Submit button based on the validity of the entire form, not just a field.
The accepted answer seems to not work using the latest Flutter v1.7.8 (stable), it gives me the following error:
This TestForm widget cannot be marked as needing to build because the framework is already in the process of building widgets
The working version looks like the following:
...
autovalidate: true,
validator: (String txt){
bool isValid = txt.length == 10;
if (isValid != _btnEnabled) {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_btnEnabled = txt.length == 10;
});
});
}
}
...
You can use this method .
bool isEnable = false;
void validateButton() {
bool isValid = true;
isValid = userEmail.isNotEmpty &&
userPassword.isNotEmpty &&
validateEmail(userEmail) &&
userPassword.length >= 8;
setState(() {
isEnable = isValid;
});
}
now in your Textfield onChanged method you have to call this function
like This
onChanged: (email) {
userEmail = email;
setState(() {});
validateButton();
},
and in your Login Button
isEnable?ActiveButton():DisableButton()
Get error "setState() or markNeedsBuild() called during build." with accepted answer. Just add Future.delayed(Duration.zero).then(....) as a trick solution, it's working on flutter 1.12.13
TextFormField(
...
autovalidate: true,
validator: (String txt){
if (txt.length == 10){
Future.delayed(Duration.zero).then((_){
setState((){
_btnEnabled = true;
});
});
} else {
Future.delayed(Duration.zero).then((_){
setState((){
_btnEnabled = false;
});
});
}
}
....