flutter riverpod leaving screen then come back it doesn't maintain the state - flutter

So I have two screens:
-Book_screen to display all the books(click on any book to go to article_screen)
-article_screen to display articles
In article_screen, I can click on article to save it as favorites.
but when I go back to book_screen then come back to article_screen, those favorited articles doesn't show the favorited status(icon red heart).
this is my article screen code:
class ArticleENPage extends ConsumerStatefulWidget{
final String bookName;
const ArticleENPage({Key? key,#PathParam() required this.bookName,}) : super(key: key);
#override
ArticleENScreen createState()=> ArticleENScreen();
}
class ArticleENScreen extends ConsumerState<ArticleENPage> {
late Future<List<Code>> codes;
#override
void initState() {
super.initState();
codes = fetchCodes();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.bookName,style: const TextStyle(fontSize: 24,fontWeight: FontWeight.bold),),backgroundColor: Colors.white,foregroundColor: Colors.black,elevation: 0,),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: Container(
margin: const EdgeInsets.only(top:10),
height: 43,
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
border: Border.all(
color: Colors.black.withOpacity(0.32),
),
),
child: Consumer(
builder: (context,ref,_) {
return TextField(
onChanged: (value) {
searchStringController controller = ref.read(searchStringProvider.notifier);
controller.setText(value.toLowerCase());
},
decoration: const InputDecoration(
border: InputBorder.none,
icon: Icon(Icons.search,size:18),
hintText: "Search Here",
hintStyle: TextStyle(color: Color.fromRGBO(128,128, 128, 1)),
),
);
}
),
),
),
const SizedBox(height: 10),
Expanded(
child: FutureBuilder(
builder: (context, AsyncSnapshot<List<Code>> snapshot) {
if (snapshot.hasData) {
return Center(
child: Consumer(
builder: (context,ref,child) {
final searchString = ref.watch(searchStringProvider);
return ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return snapshot.data![index].name
.toLowerCase()
.contains(searchString) ||
snapshot.data![index].description
.toLowerCase()
.contains(searchString)
? Consumer(
builder: (context,ref,child) {
final favlist = ref.watch(FavoriteListController.favoriteListProvider);
print(favlist);
final alreadySaved = favlist.contains(snapshot.data![index]);
return Card(
child:Padding(
padding: const EdgeInsets.all(10),
child:ExpandableNotifier(
child: ScrollOnExpand(
child: ExpandablePanel(
theme: const ExpandableThemeData(hasIcon: true),
header: RichText(text: TextSpan(children: highlight(snapshot.data![index].name, searchString,'title')),),
collapsed: RichText(text: TextSpan(children: highlight(snapshot.data![index].description, searchString,'content')), softWrap: true, maxLines: 3, overflow: TextOverflow.ellipsis,),
expanded: Column(
children: [
RichText(text: TextSpan(children: highlight(snapshot.data![index].description, searchString,'content')), softWrap: true ),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
semanticLabel: alreadySaved ? 'Remove from saved' : 'Save',
),
onPressed: () {
FavoriteListController controller = ref.read(FavoriteListController.favoriteListProvider.notifier);
if (alreadySaved) {
controller.toggle(snapshot.data![index]);
} else {
controller.toggle(snapshot.data![index]);
}
},
),
IconButton(
icon: const Icon(Icons.content_copy),
onPressed: () {
setState(() {
Clipboard.setData(ClipboardData(text: snapshot.data![index].name+"\n"+snapshot.data![index].description))
.then((value) {
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(content: Text('Copied')));
},);
});
},
),],),],)),),)));})
: Container();
},
separatorBuilder: (BuildContext context, int index) {
return snapshot.data![index].name
.toLowerCase()
.contains(searchString) ||
snapshot.data![index].description
.toLowerCase()
.contains(searchString)
? Divider()
: Container();
},
);
}
),
);
} else if (snapshot.hasError) {
return const Center(child: Text('Something went wrong :('));
}
return const Align(alignment:Alignment.topCenter,child:CircularProgressIndicator());
},
future: codes,
),
),
],
),
);
}
//read from files
Future<List<Code>> fetchCodes() async {
final response =
await rootBundle.loadString('assets/articles.json');
var CodeJson = json.decode(response)[widget.bookName] as List<dynamic>;
return CodeJson.map((code) => Code.fromJson(code)).toList();
}
}
I tried using riverpod for provider and save to sharedpreference the list of code that I favorited.
final sharedPrefs =
FutureProvider<SharedPreferences>((_) async => await SharedPreferences.getInstance());
class FavoriteListController extends StateNotifier<List<Code>>{
FavoriteListController(this.pref) : super(Code.decode(pref?.getString("favcode")??""));
static final favoriteListProvider = StateNotifierProvider<FavoriteListController, List<Code>>((ref) {
final pref = ref.watch(sharedPrefs).maybeWhen(
data: (value) => value,
orElse: () => null,
);
print(pref?.getString("favcode"));
return FavoriteListController(pref);
});
final SharedPreferences? pref;
void toggle(Code code) {
if (state.contains(code)) {
state = state.where((id) => id != code).toList();
} else {
state = [...state, code];
}
final String encodedData = Code.encode(state);
pref!.setString("favcode", encodedData);
}
}
I am not sure what is the cause of this but I think it might be because of futurebuilder? I am confused to how to solve this issue...
I am stuck in a dead end so any help or advice would be really appreciated
edit 1-
this is my source code in case I have not include all the necessary codes
https://github.com/sopheareachte/LawCode
edit-2
do I need to change "late Future<List> codes;" that fetch all the codes for futurebuilder to riverpod futureprovider too for it to work?

Maybe the problem is, that you define a static provider inside of your controller class. Try this code:
final sharedPrefs = FutureProvider<SharedPreferences>((_) async => await SharedPreferences.getInstance());
final favoriteListProvider = StateNotifierProvider<FavoriteListController, List<Code>>((ref) {
final pref = ref.watch(sharedPrefs).maybeWhen(
data: (value) => value,
orElse: () => null,
);
print(pref?.getString("favcode"));
return FavoriteListController(pref);
});
class FavoriteListController extends StateNotifier<List<Code>>{
FavoriteListController(this.pref) : super(Code.decode(pref?.getString("favcode")??""));
final SharedPreferences? pref;
void toggle(Code code) {
if (state.contains(code)) {
state = state.where((id) => id != code).toList();
} else {
state = [...state, code];
}
final String encodedData = Code.encode(state);
pref!.setString("favcode", encodedData);
}
}

Related

Error : Failed to detect image file format using the file header. File header was [0x3c 0x68 0x74 0x6d 0x6c 0x3e 0x0a 0x20 0x20 0x20]

I'm trying to display an image from Firestore in to my UI. But it returns the error above, and the file format also changes when I run it on Edge (not on physical device or Emulator). I can't figure out how. Here's my code.
class ViewPola extends StatefulWidget {
const ViewPola({Key? key}) : super(key: key);
static String id = 'view_pola';
#override
State<ViewPola> createState() => _ViewPolaState();
}
class _ViewPolaState extends State<ViewPola> {
Function getSnaps = () async {
final polaMap = {
'Kode Pola': '',
'Bagian Pola': '',
'image_url': '',
};
FirebaseFirestore.instance
.collection('PolaWillyJKT')
.limit(1)
.get()
.then((snapshot) {
if (snapshot.size == 0) {
FirebaseFirestore.instance.collection('PolaWillyJKT').add(polaMap);
FirebaseFirestore.instance.collection('PolaWillyJKT');
print('add');
} else {
print('disini nge get');
var a = FirebaseFirestore.instance.collection('PolaWillyJKT').get();
Map<String, dynamic> data = a as Map<String, dynamic>;
print(data);
}
});
};
var _selectedItem;
var _showList = false;
#override
void initState() {
getSnaps();
super.initState();
}
final Stream<QuerySnapshot> _polaWilly =
FirebaseFirestore.instance.collection('PolaWillyJKT').snapshots();
#override
Widget build(BuildContext context) {
final screenHeight = ScreenInfo.screenHeight(context);
final screenWidth = ScreenInfo.screenWidth(context);
return StreamBuilder<QuerySnapshot>(
stream: _polaWilly,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return SafeArea(
child: Scaffold(
body: Padding(
padding: EdgeInsets.all(25),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
'Pilih Bagian Pola',
style: TextStyle(fontSize: 25),
),
const SizedBox(
height: 20,
),
DropdownButton(
isExpanded: true,
value: _selectedItem,
items: snapshot.data?.docs
.map(
(value) => DropdownMenuItem(
value: value.get("Bagian Pola"),
child: Text('${value.get("Bagian Pola")}'),
),
)
.toList(),
onChanged: (newValue) {
setState(() {
_selectedItem = newValue.toString();
_showList = true;
});
}),
Padding(
padding: const EdgeInsets.all(25),
child: Visibility(
visible: _showList,
child: Container(
height: screenHeight * 0.4,
child: ListView(
children: snapshot.data!.docs
.where(
(e) => e.get("Bagian Pola") == _selectedItem)
.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return Center(
child: Column(
children: [
Text(
'Bagian Pola: ${data["Bagian Pola"]}',
style: TextStyle(fontSize: 15),
),
const SizedBox(
height: 15,
),
Text(
'Kode Pola : ${data["Kode Pola"]}',
style: TextStyle(fontSize: 15),
),
const SizedBox(
height: 15,
),
Image(
image:
NetworkImage(document.get("Foto Pola")),
height: 200,
width: 200,
), // this is the Image widget where I tried to put the image from firestore
Text(document.get('Foto Pola').toString()),
],
),
);
}).toList(),
),
),
),
),
],
),
),
),
);
},
);
}
}
and here's the class that uploads the image to Firestore
class Input_Pola extends StatefulWidget {
const Input_Pola({Key? key}) : super(key: key);
static String id = 'input_pola';
#override
State<Input_Pola> createState() => _Input_PolaState();
}
class _Input_PolaState extends State<Input_Pola> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
void _checkDuplicate() {
if (options.contains(_textfieldValue.text)) {
// Display bottom sheet that says item already exists
_scaffoldKey.currentState?.showBottomSheet((context) => Container(
height: 300,
child: Column(
children: [
const Text('Item already exists'),
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Dismiss'))
],
),
));
} else {
// Add item to options list
setState(() {
options.add(_textfieldValue.text);
});
}
}
XFile? image;
final ImagePicker picker = ImagePicker();
String _image_url = '';
void _addToFirebase(String dropdownValue, String _kodePolaController) {
var imageFile = File(image!.path);
String fileName = pth.basename(imageFile.path);
FirebaseStorage storage = FirebaseStorage.instance;
Reference ref = storage.ref().child("WillyJKT/polaWillyJKT");
UploadTask uploadTask = ref.putFile(imageFile);
uploadTask.whenComplete(() async {
var url = await ref.getDownloadURL();
_image_url = url.toString();
});
FirebaseFirestore.instance
.collection('PolaWillyJKT')
.doc(_selectedOption)
.set({
'Bagian Pola': _selectedOption,
'Kode Pola': _kodePolaController,
'Foto Pola': _image_url
});
}
String _dropdownValue = 'kg';
String _property1 = '';
String _property2 = '';
String _property3 = '';
bool _isOptionSelected = false;
final TextEditingController _kodePolaController = TextEditingController();
var _selectedOption;
final TextEditingController _textfieldValue = TextEditingController();
final List<String> options = [];
#override
void initState() {
super.initState();
_selectedOption = options.isNotEmpty ? options[0] : null;
}
//we can upload image from camera or from gallery based on parameter
Future getImage(ImageSource media) async {
var img = await picker.pickImage(source: media);
setState(() {
image = img;
});
}
void myAlert() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
title: Text('Please choose media to select'),
content: Container(
height: MediaQuery.of(context).size.height / 6,
child: Column(
children: [
ElevatedButton(
//if user click this button, user can upload image from gallery
onPressed: () {
Navigator.pop(context);
getImage(ImageSource.gallery);
},
child: Row(
children: [
Icon(Icons.image),
Text('From Gallery'),
],
),
),
ElevatedButton(
//if user click this button. user can upload image from camera
onPressed: () {
Navigator.pop(context);
getImage(ImageSource.camera);
},
child: Row(
children: [
Icon(Icons.camera),
Text('From Camera'),
],
),
),
],
),
),
);
});
}
#override
Widget build(BuildContext context) {
final screenHeight = ScreenInfo.screenHeight(context);
final screenWidth = ScreenInfo.screenWidth(context);
return SafeArea(
child: Scaffold(
key: _scaffoldKey,
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
_addToFirebase(_dropdownValue, _kodePolaController.text);
},
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(25),
child: Container(
height: screenHeight,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextFormField(
decoration: const InputDecoration(
hintText: 'Input Pola',
border: UnderlineInputBorder(),
),
onChanged: (value) {
setState(() {
_textfieldValue.text = value;
});
},
),
DropdownButton<String>(
value: _selectedOption,
onChanged: (value) {
setState(() {
_selectedOption = value!;
_isOptionSelected = true;
_kodePolaController.clear();
});
},
hint: const Text('Input from Text Field Above'),
items: options.map((option) {
return DropdownMenuItem<String>(
value: option,
child: Text(option),
);
}).toList(),
),
TextButton(
onPressed: _checkDuplicate,
child: const Text("Add Option"),
),
Visibility(
visible: _isOptionSelected,
child: Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
TextField(
controller: _kodePolaController,
decoration: const InputDecoration(
labelText: "Kode Pola"),
onChanged: (value) {
setState(() {
_property1 = value;
});
},
),
const SizedBox(height: 20,),
ElevatedButton(
onPressed: () {
myAlert();
},
child: Text('Upload Photo'),
),
SizedBox(
height: 10,
),
//if image not null show the image
//if image null show text
image != null
? Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.file(
//to show image, you type like this.
File(image!.path),
fit: BoxFit.cover,
width: MediaQuery.of(context).size.width,
height: 300,
),
),
)
: const Text(
"No Image",
style: TextStyle(fontSize: 20),
),
],
),
),
)
],
),
),
),
),
),
);
}
}
It also displays the error on Edge, but when I build the apk and run it on my physical device, it just shows nothing on the screen except for the data that's got above it. Any idea on how I can fix this?
var a = FirebaseFirestore.instance.collection('PolaWillyJKT').get();
This returns a promise but not the actual data. In fact it could still be fetching the data when you attempt to convert it to a map.
You can await
var a = await FirebaseFirestore.instance.collection('PolaWillyJKT').get();
myDoc = a.docs.first();
Or use a callback that will run once the collection fetch completes:
FirebaseFirestore.instance.collection('PolaWillyJKT').get().then((response) => {
print(response);
});

getx obx not updating avatar image - Flutter GetX

what I want to achieve is to change the image in CircleAvatar when I'm selecting an image, here is the code:
ProfileController:
class ProfileController extends GetxController {
final TextEditingController emailController = TextEditingController();
final ImagePicker _picker = ImagePicker();
Rx<String?> avatarPath = null.obs;
avatarFromCamera() async {
var localAvatar = await _picker.pickImage(
source: ImageSource.camera, imageQuality: 50
);
if (localAvatar != null) {
avatarPath = localAvatar.path.obs;
update();
}
}
avatarFromGallery() async {
var localAvatar = await _picker.pickImage(
source: ImageSource.gallery, imageQuality: 50
);
if (localAvatar != null) {
avatarPath = localAvatar.path.obs;
update();
}
}
String? emailValidator(String? value) {
if (value == null || value.isEmpty) {
return null;
}
if (!EmailValidator.validate(value, false)) {
return 'Invalid email address';
}
}
#override
void onClose() {
emailController.dispose();
super.onClose();
}
String? emailValidator(String? value) {
if (value == null || value.isEmpty) {
return null;
}
if (!EmailValidator.validate(value, false)) {
return 'Invalid email address';
}
}
void save(GlobalKey<FormState> profileFormKey) {
if (profileFormKey.currentState!.validate()) {
print('valid');
}
}
}
and here is the ProfileScreen widget:
lass ProfileScreen extends StatelessWidget {
final ProfileController _profileController = Get.put<ProfileController>(ProfileController());
GlobalKey<FormState> profileFormKey = GlobalKey<FormState>();
ProfileScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Update user details'),
),
body: SingleChildScrollView(
child: Form(
key: profileFormKey,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(30.0),
child: TextFormField(
keyboardType: TextInputType.text,
controller: _profileController.emailController,
decoration: const InputDecoration(
labelText: 'Enter email',
),
validator: _profileController.emailValidator,
),
),
Center(
child: GestureDetector(
onTap: () {
showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return SafeArea(
child: Wrap(
children: <Widget>[
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text('Photo Library'),
onTap: () {
_profileController.avatarFromGallery();
Navigator.of(context).pop();
}),
ListTile(
leading: const Icon(Icons.photo_camera),
title: const Text('Camera'),
onTap: () {
_profileController.avatarFromCamera();
Navigator.of(context).pop();
},
),
],
),
);
}
);
},
child: CircleAvatar(
radius: 55,
backgroundColor: Colors.pink,
child: Obx(() =>(_profileController.avatarPath.value != null)
? ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.file(
File(_profileController.avatarPath.value!),
width: 100,
height: 100,
fit: BoxFit.fitHeight
),
)
: Container(
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(50)),
width: 100,
height: 100,
child: Icon(
Icons.camera_alt,
color: Colors.grey[800],
),
),
),
),
)),
Container(
margin: const EdgeInsets.all(10),
width: double.infinity,
child: MaterialButton(
color: Colors.blue,
onPressed: () => _profileController.save(profileFormKey),
child: const Text(
'Submit',
style: TextStyle(color: Colors.white),
),
),
),
],
),
),
),
);
}
}
as you can see, I have Obx and my avatarPath reactive, and I'm running update everywhere I changing it, but it's not udpated. I also tried to use empty string as initial value of imagePath like this Rx<String> avatarPath = ''.obs; and it's not working. What I'm doing wrong??? Thank you in advice!!!
There are two things to revise for it. Firstly, change avatarPath = localAvatar.path.obs; with avatarPath.value = localAvatar.path;. Because localAvatar.path.obs create the new observable and changes will not be reflected to previous observers.
Secondly, create a new stateless widget having the widget tree of bottom sheet's builder like
showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return CustomBottomView();
}
);
Then inside the CustomBottomView copy your bottom sheet widget tree.
class CustomBottomView extends GetView<ProfileController> {
return YourBottomSheetWidgetTreeHere();
}
Dont worry about ProfileController here. You have already put it in DI in previous route. First follow the first step if you still face the problem second step will definitely resolve it.

RenderFlex children have non-zero flex but incoming height constraints are unbounded in flatter

I have a page code written in an android studio. On this page, I display a list of objects, as well as a widget to search for these objects. When I clicked on the search widget, I had the following problem: "The following statement was thrown during layout:
RenderFlex is overcrowded 31 pixels below.
The corresponding widget that caused the errors was: "But then I found the answer to this question and added" SingleChildScrollView ".
But then I had this problem: "RenderFlex have non-zero flexibility, but the input height limits are unlimited." . And I can't solve it in any way. I will be grateful for your help. Here is my code:
import 'package:flutter/material.dart';
import 'package:flutter_app_seals/model/post/form_unseals.dart';
import 'package:flutter_app_seals/model/post/form_seals.dart';
import 'package:flutter_app_seals/model/setting/globalvar.dart' as global;
import 'dart:async';
import 'dart:io';
import 'dart:convert';
void main() => runApp(JsonParseObjectSts_UN());
class JsonParseObjectSts_UN extends StatefulWidget {
JsonParseObjectSts_UN() : super();
#override
_JsonParseObjectsState createState() => _JsonParseObjectsState();
}
class _JsonParseObjectsState extends State <StatefulWidget> {
List<UserDetails> _searchResult = [];
List<UserDetails> _userDetails = [];
TextEditingController controller = new TextEditingController();
final String url = global.urlVar ;
// Get json result and convert it to model. Then add
Future<Null> getUserDetails() async {
HttpClient client = new HttpClient();
client.badCertificateCallback = ((X509Certificate cert, String host, int port) => true);
final request = await client
.getUrl(Uri.parse(url))
.timeout(Duration(seconds: 5));
HttpClientResponse response = await request.close();
var responseBody = await response.transform(utf8.decoder).join();
final responseJson = json.decode(responseBody);
setState(() {
for (Map user in responseJson) {
_userDetails.add(UserDetails.fromJson(user));
}
});
}
#override
void initState() {
super.initState();
getUserDetails();
}
Widget _buildUsersList() {
return new ListView.builder(
itemCount: _userDetails.length,
itemBuilder: (context, index) {
return new Card(
color: (_userDetails[index].sealed == "Так") ? Colors.redAccent : Colors.greenAccent,
margin: EdgeInsets.symmetric(vertical: 7),
child: ListTile(
title: Text(
_userDetails[index].name,
style: TextStyle(fontSize: 25),
),
subtitle: Text("Запломбований:${_userDetails[index].sealed}"),
leading: Icon(
Icons.home_outlined,
size: 30,
color: Colors.black87,
),
onTap: () =>
{
if ('Так' == _userDetails[index].sealed) {
global.nameObj = _userDetails[index].name,
global.sealsNumb = _userDetails[index].seal_number,
global.typesOp = 'Так',
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Unseals()),
)
}
else{
{
global.nameObj = _userDetails[index].name,
global.typesOp = 'Ні',
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Form_seals()),
)
}
}
}
),
);
},
);
}
Widget _buildSearchResults() {
return new ListView.builder(
itemCount: _searchResult.length,
itemBuilder: (context, i) {
return new Card(
color: (_searchResult[i].sealed == "Так") ? Colors.redAccent : Colors.greenAccent,
margin: EdgeInsets.symmetric(vertical: 7),
child: ListTile(
title: Text(
_searchResult[i].name,
style: TextStyle(fontSize: 25),
),
subtitle: Text("Запломбований:${_searchResult[i].sealed}"),
leading: Icon(
Icons.home_outlined,
size: 30,
color: Colors.black87,
),
onTap: () =>
{
if ('Так' == _searchResult[i].sealed) {
global.nameObj = _searchResult[i].name,
global.sealsNumb = _searchResult[i].seal_number,
global.typesOp = 'Так',
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Unseals()),
)
}
else{
{
global.nameObj = _searchResult[i].name,
global.typesOp = 'Ні',
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Form_seals()),
)
}
}
}
),
);
},
);
}
Widget _buildSearchBox() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Card(
child: new ListTile(
leading: new Icon(Icons.search),
title: new TextField(
controller: controller,
decoration: new InputDecoration(
hintText: 'Пошук', border: InputBorder.none),
onChanged: onSearchTextChanged,
),
trailing: new IconButton(
icon: new Icon(Icons.cancel),
onPressed: () {
controller.clear();
onSearchTextChanged('');
},
),
),
),
);
}
Widget _buildBody() {
return new Scaffold(
body:Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue, Colors.white],
begin: Alignment.topCenter,
end: Alignment.bottomCenter),
),
child: SingleChildScrollView(
child:Column(
children: <Widget>[
new Container(
color: Theme.of(context).primaryColor, child: _buildSearchBox()),
new Expanded(
child: _searchResult.length != 0 || controller.text.isNotEmpty
? _buildSearchResults()
: _buildUsersList()),
],
),
),
),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: _buildBody()
);
}
onSearchTextChanged(String text) async {
_searchResult.clear();
if (text.isEmpty) {
setState(() {});
return;
}
_userDetails.forEach((userDetail) {
if (userDetail.name.contains(text) ) _searchResult.add(userDetail);
});
setState(() {});
}
}
class UserDetails {
final String name, seal_number,sealed;
UserDetails({this.name, this.sealed, this.seal_number});
factory UserDetails.fromJson(Map<String, dynamic> json) {
return new UserDetails(
sealed: json['sealed'],
name: json['name'],
seal_number: json['seal_number'],
);
}
}
My scrin:
My scrin when I want to use search:

implement onTap in flutter

I try to create an onTap function on ChatRoomListTile which navigates me to the chatscreen where the id is = chatRoomId. Unfortunately I don't know how to deliver the ID.
Unfortunately I am very unexperienced in flutter and I made this app via tutorial... It works nice but I am still learning, so a big big big thanks!
my Database.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:signal/helperfunctions/sharedpref_helper.dart';
class DatabaseMethods{
Future addUserInfoToDB(String userId, Map<String, dynamic>userInfoMap)
async {
return FirebaseFirestore.instance
.collection("users")
.doc(userId)
.set(userInfoMap);
}
Future<Stream<QuerySnapshot>> getUserByUserName(String username) async{
return FirebaseFirestore.instance
.collection("users")
.where("username", isEqualTo: username)
.snapshots();
}
Future addMessage(String chatRoomId, String messageId, Map messageInfoMap) async {
return FirebaseFirestore.instance
.collection ("chatrooms")
.doc(chatRoomId)
.collection("chats")
.doc(messageId)
.set(messageInfoMap);
}
updateLastMessageSend(String chatRoomId, Map lastMessageInfoMap){
return FirebaseFirestore.instance
.collection("chatrooms")
.doc(chatRoomId)
.update(lastMessageInfoMap);
}
createChatRoom(String chatRoomId, Map chatRoomInfoMap) async{
final snapShot = await FirebaseFirestore.instance
.collection("chatrooms")
.doc(chatRoomId)
.get();
if(snapShot.exists){
//chatroom already exists
return true;
}else{
//chatroom does not exists
return FirebaseFirestore.instance
.collection("chatrooms")
.doc(chatRoomId)
.set(chatRoomInfoMap);
}
}
Future<Stream<QuerySnapshot>> getChatRoomMessages(chatRoomId) async {
return FirebaseFirestore.instance
.collection("chatrooms")
.doc(chatRoomId)
.collection("chats")
.orderBy("ts", descending: true)
.snapshots();
}
// Future<Stream<QuerySnapshot>> openChatRoom(String chatRoomId) async{
// return FirebaseFirestore.instance
// .collection("chatrooms")
// .doc(chatRoomId)
// .collection("chats")
// .snapshots();
// }
Future<Stream<QuerySnapshot>> getChatRooms() async {
String myUsername = await SharedPreferenceHelper().getUserName();
return FirebaseFirestore.instance
.collection("chatrooms")
.orderBy("lastMessageSendTs", descending: true)
.where("users",arrayContains: myUsername)
.snapshots();
}
Future<QuerySnapshot> getUserInfo(String username) async {
return await FirebaseFirestore.instance
.collection("users")
.where("username", isEqualTo: username)
.get();
}
}
and my home.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:signal/helperfunctions/sharedpref_helper.dart';
import 'package:signal/services/auth.dart';
import 'package:signal/services/database.dart';
import 'package:signal/views/signin.dart';
import 'services/colorpicker.dart';
import 'chatscreen.dart';
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool isSearching = false;
String myName, myProfilePic, myUserName, myEmail, messageSentTs;
Stream usersStream, chatRoomsStream;
TextEditingController searchUsernameEditingController =
TextEditingController();
getMyInfoFromSharedPreference() async{
myName = await SharedPreferenceHelper().getDisplayName();
myProfilePic = await SharedPreferenceHelper().getUserProfileUrl();
myUserName = await SharedPreferenceHelper().getUserName();
myEmail = await SharedPreferenceHelper().getUserEmail();
}
//dieser string sollte eigentlich aus sicherheitsgründen random generiert werden
getChatRoomIdByUsernames(String a, String b){
if(a.substring(0,1).codeUnitAt(0) > b.substring(0,1).codeUnitAt(0)){
return "$b\_$a";
}else{
return "$a\_$b";
}
}
onSearchBtnClick() async {
isSearching = true;
setState(() {});
usersStream = await DatabaseMethods()
.getUserByUserName(searchUsernameEditingController.text);
setState(() {});
}
Widget chatRoomsList(){
return StreamBuilder(
stream: chatRoomsStream,
builder: (context, snapshot){
return snapshot.hasData
? ListView.builder(
itemCount: snapshot.data.docs.length,
shrinkWrap: true,
itemBuilder: (context, index){
DocumentSnapshot ds = snapshot.data.docs[index];
return ChatRoomListTile(ds["lastMessage"], ds.id,myUserName);
})
: Center(child: CircularProgressIndicator());
},
);
}
// print("this is the values we have $myUserName $username"));
// Chatroom ID Kontrolle ( achtung beim randomisen der CRId)
Widget searchUserListTile({String profileUrl, name, username, email}){
return GestureDetector(
onTap: (){
var chatRoomId = getChatRoomIdByUsernames(myUserName, username);
Map<String, dynamic> chatRoomInfoMap = {
"users" : [myUserName, username]
};
DatabaseMethods().createChatRoom(chatRoomId, chatRoomInfoMap);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatScreen(username, name)
)
);
},
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(30),
child: Image.network(
profileUrl,
height: 20,
width: 20,
),
),
SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children:[
Text(name),
Text(email),
],
)
],
),
);
}
Widget searchUsersList(){
return StreamBuilder(
stream: usersStream,
builder: (context, snapshot){
return snapshot.hasData
? ListView.builder(
itemCount: snapshot.data.docs.length,
shrinkWrap: true,
itemBuilder: (context, index){
DocumentSnapshot ds = snapshot.data.docs[index];
return searchUserListTile(
profileUrl: ds["imgUrl"],
name: ds["name"],
username: ds["username"],
email: ds["email"]
);
},
) : Center(child: CircularProgressIndicator(),);
}
);
}
getChatRooms() async {
chatRoomsStream = await DatabaseMethods().getChatRooms();
setState(() {});
}
onScreenLoaded() async {
await getMyInfoFromSharedPreference();
getChatRooms();
}
#override
void initState() {
onScreenLoaded();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(233, 240, 244, 1),
appBar: AppBar(
title: Image.asset('assets/images/logo.png', width: 40,),
elevation: 0.0,
backgroundColor: Colors.white,
actions: [
InkWell(
onTap: (){
AuthMethods().signOut().then((s) {
Navigator.pushReplacement(context, MaterialPageRoute
(builder: (context) => SignIn()));
});
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Icon(Icons.exit_to_app)),
)
],
),
body: Container(
margin: EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: [
Row(
children: [
isSearching
? GestureDetector(
onTap: (){
isSearching = false;
searchUsernameEditingController.text = "";
setState(() {});
},
child: Padding(
padding: EdgeInsets.only(right: 12),
child: Icon(Icons.arrow_back)),
) : Container(),
Expanded(
child: Container(
// Disable Searchbar
width: 0,
height: 0,
margin: EdgeInsets.symmetric(vertical: 16),
padding: EdgeInsets.symmetric(horizontal:16),
decoration: BoxDecoration
(border: Border.all(
color: Colors.black87,
width: 1,
style: BorderStyle.solid), borderRadius: BorderRadius.circular(24)),
child: Row(
children: [
Expanded(
child: TextField(
controller: searchUsernameEditingController,
decoration: InputDecoration(
border: InputBorder.none, hintText: "Finde einen Freund"),
)),
GestureDetector(
onTap:(){
if(searchUsernameEditingController.text != ""){
onSearchBtnClick();
}
},
// Disable Search Icon
// child: Icon(Icons.search),
)
],
),
),
)
]
),
isSearching ? searchUsersList() : chatRoomsList()
],
),
),
);
}
}
class ChatRoomListTile extends StatefulWidget {
final String lastMessage, chatRoomId, myUsername;
ChatRoomListTile(this.lastMessage, this.chatRoomId, this.myUsername);
#override
_ChatRoomListTileState createState() => _ChatRoomListTileState();
}
class _ChatRoomListTileState extends State<ChatRoomListTile> {
String profilePicUrl ="", name="", username="";
getThisUserInfo() async {
username = widget.chatRoomId.replaceAll(widget.myUsername, "").replaceAll("_", "");
QuerySnapshot querySnapshot = await DatabaseMethods().getUserInfo(username);
name = "${querySnapshot.docs[0]["name"]}";
profilePicUrl = "${querySnapshot.docs[0]["imgUrl"]}";
setState(() {});
}
#override
void initState() {
getThisUserInfo();
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 2, horizontal: 0,),
padding: EdgeInsets.symmetric(horizontal:12,vertical: 20,),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
profilePicUrl,
height: 35,
width: 35,
),
),
SizedBox(width: 15),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: TextStyle(
fontSize: 14,
fontFamily: 'Roboto',
color: Color.fromRGBO(57, 59, 85, 0.8),
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 5),
Container(
width: (MediaQuery.of(context).size.width) - 150,
child: Text(
widget.lastMessage,
style: new TextStyle(
fontSize: 13.0,
fontFamily: 'Roboto',
color: Color.fromRGBO(171, 183, 201, 1),
fontWeight: FontWeight.w400,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
)
],
),
);
}
}
you can pass the id as a constructor from the listTile,
Navigator.of(context).push.... ChatScreen(id: chatRoomId)
as so, and in ChatScreen you can receive it like so
class ChatScreenextends StatefulWidget {
final String id;
ChatScreen({requried this.id});
#override
_ChatScreenState createState() => _ChatScreenState ();
}
class _ChatScreenState extends State<ChatScreen> {
if it is a stateless Widget class you can directly access the id as id, if it is a stateful Widget then widget.id ,
Another way of doing this is passing as Navigator argument you can check the docs for more info Flutter Docs
Hello and thank you very much for your time ... i tried to implement it but unfortunate i get an error. here the GestureDetector i added to my home.dart ::: I also tried to change id: chatRoomId to this.Id or just chatRoomId .. but it keeps underlined and erroring :
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => ChatScreen(id: chatRoomId)));
},
child: Container(
margin: EdgeInsets.symmetric(vertical: 2, horizontal: 0,),
padding: EdgeInsets.symmetric(horizontal:12,vertical: 20,),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
and here the update of the chatscreen.dart
class ChatScreen extends StatefulWidget {
final String chatWidthUsername, name, id;
ChatScreen(this.chatWidthUsername, this.name, {this.id});
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
String chatRoomId, messageId = "";
Stream messageStream;
String myName, myProfilePic, myUserName, myEmail;
TextEditingController messageTextEditingController = TextEditingController();
thanks again so much !
greetings
alex

how to use useStreamController in HookWidget?

Im new to flutter hooks, and riverpod(state-manangement),
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _url = "https://owlbot.info/api/v4/dictionary/";
String _token = "ae7cbdfff57e548a4360348ee519123a741d8e3d";
TextEditingController _controller = TextEditingController();
StreamController _streamController;
Stream _stream;
Timer _debounce;
Future _search() async {
if (_controller.text == null || _controller.text.length == 0) {
_streamController.add(null);
return;
}
_streamController.add("waiting");
Response response = await get(_url + _controller.text.trim(),
headers: {"Authorization": "Token " + _token});
_streamController.add(json.decode(response.body));
}
#override
void initState() {
super.initState();
_streamController = StreamController();
_stream = _streamController.stream;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flictionary"),
bottom: PreferredSize(
preferredSize: Size.fromHeight(48.0),
child: Row(
children: <Widget>[
Expanded(
child: Container(
margin: const EdgeInsets.only(left: 12.0, bottom: 8.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24.0),
),
child: TextFormField(
onChanged: (String text) {
if (_debounce?.isActive ?? false) _debounce.cancel();
_debounce = Timer(const Duration(milliseconds: 1000), () {
_search();
});
},
controller: _controller,
decoration: InputDecoration(
hintText: "Search for a word",
contentPadding: const EdgeInsets.only(left: 24.0),
border: InputBorder.none,
),
),
),
),
IconButton(
icon: Icon(
Icons.search,
color: Colors.white,
),
onPressed: () {
_search();
},
)
],
),
),
),
body: Container(
margin: const EdgeInsets.all(8.0),
child: StreamBuilder(
stream: _stream,
builder: (BuildContext ctx, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Center(
child: Text("Enter a search word"),
);
}
if (snapshot.data == "waiting") {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
itemCount: snapshot.data["definitions"].length,
itemBuilder: (BuildContext context, int index) {
return ListBody(
children: <Widget>[
Container(
color: Colors.grey[300],
child: ListTile(
leading: snapshot.data["definitions"][index]
["image_url"] ==
null
? null
: CircleAvatar(
backgroundImage: NetworkImage(snapshot
.data["definitions"][index]["image_url"]),
),
title: Text(_controller.text.trim() +
"(" +
snapshot.data["definitions"][index]["type"] +
")"),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
snapshot.data["definitions"][index]["definition"]),
)
],
);
},
);
},
),
),
);
}
}
i just wanted to convert above statefulWidget to HookWidget and how to use riverpod as statemanagenent for the above example. I know some basics of hooks and riverpod, but still i'm confused between the hooks, statemanagement(riverpod).
Please Can someone help understand them and provide some examples or at least convert the above code into the hook widget, and using hookbuilder
Thanks in advance
First off, the code:
final textProvider = StateProvider<String>((_) => '');
final responseFutureProvider =
FutureProvider.autoDispose.family<Response, String>((ref, text) async {
if (text == null || text.length == 0) {
throw Error();
}
final String _url = "https://owlbot.info/api/v4/dictionary/";
final String _token = "ae7cbdfff57e548a4360348ee519123a741d8e3d";
return await get(_url + text.trim(), headers: {"Authorization": "Token " + _token});
});
final responseProvider = Computed<AsyncValue<Response>>((read) {
final text = read(textProvider).state;
return read(responseFutureProvider(text));
});
String _useDebouncedSearch(TextEditingController controller) {
final search = useState(controller.text);
useEffect(() {
Timer? timer;
void listener() {
timer?.cancel();
timer = Timer(
const Duration(milliseconds: 1000),
() => search.value = controller.text,
);
}
controller.addListener(listener);
return () {
timer?.cancel();
controller.removeListener(listener);
};
}, [controller]);
return search.value;
}
class MyHomePage extends HookWidget {
const MyHomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final controller = useTextEditingController();
final text = useProvider(textProvider);
text.state = _useDebouncedSearch(controller);
return Scaffold(
appBar: AppBar(
title: Text("Flictionary"),
bottom: PreferredSize(
preferredSize: Size.fromHeight(48.0),
child: Row(
children: <Widget>[
Expanded(
child: Container(
margin: const EdgeInsets.only(left: 12.0, bottom: 8.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24.0),
),
child: TextFormField(
controller: controller,
decoration: InputDecoration(
hintText: "Search for a word",
contentPadding: const EdgeInsets.only(left: 24.0),
border: InputBorder.none,
),
),
),
),
Icon(
Icons.search,
color: Colors.white,
),
],
),
),
),
body: MyHomePageBody(),
);
}
}
class MyHomePageBody extends HookWidget {
const MyHomePageBody({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final text = useProvider(textProvider).state;
final response = useProvider(responseProvider);
response.when(
error: (err, stack) => Center(child: Text('Error: $err')),
loading: () => Center(child: CircularProgressIndicator()),
data: (response) => ListView.builder(
itemCount: Response["definitions"].length,
itemBuilder: (BuildContext context, int index) {
return ListBody(
children: <Widget>[
Container(
color: Colors.grey[300],
child: ListTile(
leading: response["definitions"][index]["image_url"] == null
? null
: CircleAvatar(
backgroundImage:
NetworkImage(response["definitions"][index]["image_url"]),
),
title: Text(text.trim() + "(" + response["definitions"][index]["type"] + ")"),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(response["definitions"][index]["definition"]),
)
],
);
},
),
);
}
}
We add an external text provider so that we can read the text field from other providers.
We create a FutureProviderFamily so that we can perform the API call with a parameter, the text from your text field. In Riverpod, families enable the passing parameters to providers.
We create a Computed that will call the Future every time the value of the text provider changes. This returns an AsyncValue which is a wonderful replacement to the StreamBuilder you were using (will explain more).
Refactored your debounced search a bit to use the useEffect hook. This will handle disposing the resources for your timer and update the textProvider as necessary. (I learned this from Remi's Marvel example)
We no longer need an onChanged or manual button press to search, as the textprovider's state is being updated whenever the controller changes.
Moved the body of your page into its own class to separate what needs to be loaded from what is static.
Now, instead of a StreamBuilder, we can use AsyncValue to handle the loading, error, and success states of your build.
I know this was a lot to cover, so I'd recommend really digging into the docs to learn more about everything that's in this example.