I have to get messages from mobile so I am using this plugin. It is working but I get no messages, just printing the result Instance of 'SmsMessage' in the console, but I did everything specified in document example. did I make any mistakes?
source code
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:sms/sms.dart';
class MessagesScreen extends StatefulWidget {
MessagesScreen({Key key}) : super(key: key);
#override
_MessagesScreenState createState() => _MessagesScreenState();
}
class _MessagesScreenState extends State<MessagesScreen> {
List _allMessages;
#override
void initState() {
super.initState();
getAllMessages();
}
Future getAllMessages() async {
SmsQuery query = new SmsQuery();
List<SmsMessage> messages = await query.getAllSms;
debugPrint("Total Messages : " + messages.length.toString());
print(messages);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Messages"),
),
body: ListView.builder(
itemCount: 1,
itemBuilder: (BuildContext context, int index) {
return Text("Test");
},
),
);
}
}
This has nothing to do with SMS messages, you just cannot print a list of custom objects that way.
Go through the list in a loop and print the property of every message that you want to print. Just like you would have to do with any other complex object.
The behavior you describe is actually normal. The messages will be returned as an instance of the SmsMessage class.
When you try to print an object, it is converted to a string. By default the toString method will display Instance of "Class name"; and in your case, that's why you are having that output, you are trying to print a list of objects.
If for each of the messages, you would like to display the body, this code snippet should help you:
Future getAllMessages() async {
SmsQuery query = new SmsQuery();
List<SmsMessage> messages = await query.getAllSms;
debugPrint("Total Messages : " + messages.length.toString());
messages.forEach((element) { print(element.body); });
}
Related
In my use case, I have to change the layout of the app with JSON data. I have a JSON file which I want to get and use the key values without using the Future method in the next method rather I want to place the mapped JSON and place it in empty curly brackets:
This is the JSON file I grab:
# test_json.json
{
"fontSize" : "10",
"fontFamily" : "A",
"fontWeigth" : "bold",
"fontColor" : "blue"
}
This is the file that grabs the JSON file and maps it:
# get_json.dart
class GetJson{
Future<Map<String, dynamic>> getJson() async {
String jsonData =
await rootBundle.loadString('assets/json/test_json.json');
Map<String, dynamic> data = jsonDecode(jsonData);
return data;
}
}
Then I grab this mapped JSON and I want to place it inside a variable called mappedData and place it inside empty curly brackets. Then I want to get the number with getNumber() and inside this method I convert the type of fontSize from string to double with another custom method called TypeConvertor.getDouble():
class Utility {
var mappedData= {};
setJson() async {
mappedData = await GetJson().getJson();
}
getNumber(String key) {
var data = mappedData;
return TypeConvertor.getDouble(data[key]);
}
}
In my use case, i need to do this like this I have no other choice. I want to explicitly grab the JSON like that and I don't want getNumber() to be a Future. Then i cannot place Utility().getNumber("fontSize") inside a stateful widget because then I have to use setState and I want to avoid that because I will have a lot of keys beside fontSize and so then I have to use setState for every key values. I just want to use Utility().getNumber("fontSize") inside property fontSize and The rest also like this. In my usecase I have to do it like that:
class TestView extends StatefulWidget {
const TestView({Key? key}) : super(key: key);
#override
State<TestView> createState() => _TestViewState();
}
class _TestViewState extends State<TestView> {
#override
Widget build(BuildContext context) {
return Text(
"test",
style: TextStyle(fontSize: Utility().getNumber("fontSize")),
);
}
}
But in my app mappedData gives null. The full error is : Unhandled Exception: type 'Null' is not a subtype of type 'String' and the null value is inside mappedData. I want to grab the json data and place it inside an empty map and use the mapped json from there on. How do i resolve the null execption issue?
EDIT
Change variables to correct versions
Probably it's because you don't call setJson before call getNumber.
The following code is work.
final utility = Utility();
await utility.setJson();
print(utility.getNumber("fontSize"));
If you want to avoid similar mistakes, you have some options as solutions.
Include mappedData to Utility's constructor.
Change getNumber to static, and add argument mappedData.
Use JsonSerializable(It's a little difficult but the best solution.)
I found the solution which is partly contributed by #bakatsuyuki. So i did use await utility.setJson(); but i also initilized it with initState() so field utility has no null value. I also used FutureBuilder() to check if snapshot.hasData and then i display the Text() widget with the data from utilitiy else show empty Container. This way i can resolve the null Exception.
This is the view that worked for me:
class AppView extends StatefulWidget {
const AppView({
Key? key,
}) : super(key: key);
#override
_AppViewState createState() => _AppViewState();
}
class _AppViewState extends State<AppView> {
final utility = Utility();
Future setUtility() async {
await utility.setJson();
}
#override
void initState() {
setUtility();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder(
future: AppContent().getAppContent(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text("test",
style: TextStyle(
fontSize: utility.getNumber("fontSize"),
));
} else {
return Container();
}
}),
),
);
}
}
I've a question:
In my Widget build(BuildContext context), I want to store a certain value,
final userName = book.owner
(book is the reference to the certain value from Firestore)
But it's done not in the right way to my lack of knowledge. I'd appreciate if someone could guide through that.
Thank you in advance!
Snippet of my code
class BookView extends StatefulWidget {
final Book book;
BookView({Key key, #required this.book}) : super(key: key);
DatabaseMethods databaseMethods = new DatabaseMethods();
var userName;
#override
_BookViewState createState() => _BookViewState(book);
}
class _BookViewState extends State<BookView> {
Book book;
_BookViewState(this.book);
String userName;
#override
void initState() {
userName = book.owner;
super.initState();
}
// final Book book;
createChatroomAndStartConversation({var userName}) {
if (userName != Constants.myName) {
String roomId = getChatRoomId(userName, Constants.myName);
List<String> users = [userName, Constants.myName];
Map<String, dynamic> chatRoomMap = {
"Users": users,
"roomId": roomId,
};
DatabaseMethods().createChatRoom(roomId, chatRoomMap);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConversationScreen(roomId, userName)),
);
} else {
print("You cannot send msg to your self");
}
}
#override
Widget build(BuildContext context) {
//widget.book;
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
...
FlatButton(
child: Text(
"Get contact with",
style: TextStyle(color: Colors.white),
),
color: Colors.blue,
onPressed: () {
createChatroomAndStartConversation(
userName: userName);
...
}
Snippet of Value not in range: 1
getChatRoomId(String a, String b) {
if (a.substring(0, 1).codeUnitAt(0) > b.substring(0, 1).codeUnitAt(0)) {
return "$b\_$a";
} else {
return "$a\_$b";
}
}
It's not a good practice to store any data in build() method, because this method is invoked too many times to do the such kind of move. Consider using StatefulWidget to store any state you have in the widget, for the very beginning. When you use this widget, you can define this all in such way:
class YourWidget extends StatefulWidget {
#override
_YourWidgetState createState() => _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
String userName;
#override
void initState() {
userName = book.owner;
super.initState()
}
#override
Widget build(BuildContext context) {
return Container(child: Text(userName),);
}
}
Here, in initState() you can retrieve value from book and set it to userName. But for more complex and bigger applications, consider using StateManagement solutions and some kind of architectural patterns i.e. Riverpod, Provider, MobX, BLoC.. Because changing the state via setState() method will cause rebuilding whole child widget tree, which could freeze whole UI in complex app.
UPD to 'Snippet of my code':
According to your code, if you are using a 'book' from Widget, not its state - use widget.book, in such way you have access to widget members, because of this you don't need a constructor of state. So, due to these changes, your code might looks like:
class BookView extends StatefulWidget {
final Book book;
BookView({Key key, #required this.book}) : super(key: key);
// You DON'T need this here, because you are retrieving these methods
// inside your state via DatabaseMethods constructor
DatabaseMethods databaseMethods = DatabaseMethods();
#override
_BookViewState createState() => _BookViewState(book);
}
class _BookViewState extends State<BookView> {
String userName;
#override
void initState() {
// Using widget.book to retrieve Book object from state's widget
userName = widget.book.owner;
super.initState();
}
createChatroomAndStartConversation({var userName}) {
if (userName != Constants.myName) {
String roomId = getChatRoomId(userName, Constants.myName);
// Also, it's just a recommendation, try to omit local variables types
// because they are already known with List type (String). Also, this
// all is about chatRoomMap
var users = <String>[userName, Constants.myName];
final chatRoomMap = <String, dynamic>{
"Users": users,
"roomId": roomId,
};
DatabaseMethods().createChatRoom(roomId, chatRoomMap);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConversationScreen(roomId, userName)),
);
} else {
print("You cannot send msg to your self");
}
}
#override
Widget build(BuildContext context) {
// your widgets here
}
}
UPD 2:
Second trouble and issue with 'Snippet of Value not in range: 1'. I could to reproduce it with given value of 'a' as empty string. So, your function invocation is like getChatRoomId('', 'user123'), because of empty 'userName', substring function can't take values from range [0, 1), so exception is raised.
Please check out this 36 seconds video for more clarity, cause it was getting too verbose explaning things : https://www.youtube.com/watch?v=W6WdQuLjrCs
My best guess
It's due to the provider.
App structure ->
Outer Page -> NoteList Page
The Outer Page code :
class OuterPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return OuterPageState();
}
}
class OuterPageState extends State<OuterPage> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
int _selectedTab = 0;
var noteList;
final _pageOptions = [
NoteList(),
AnotherPageScreen(),
];
#override
Widget build(BuildContext context) {
var noteProvider = Provider.of<NotesProvider>(context, listen: false);
var customFabButton;
if (_selectedTab == 0) {
customFabButton = FloatingActionButton(
// Password section
onPressed: () {
navigateToDetail(context, Note('', '', 2), 'Add Note');
},
child: Icon(Icons.add),
);
~~~ SNIP ~~~
The Notes Tab aka NoteList page code :
class NoteList extends StatefulWidget {
NoteList();
#override
NoteListState createState() => NoteListState();
}
class NoteListState extends State<NoteList> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
List<Note> noteList;
int count = 0;
#override
Widget build(BuildContext context) {
Provider.of<NotesProvider>(context).getNotes();
return Scaffold(
key: _scaffoldKey,
body: Provider.of<NotesProvider>(context).count > 0
? NoteListScreen(_scaffoldKey)
: CircularProgressIndicator());
}
}
For full code : check here : https://github.com/LuD1161/notes_app/tree/reusable_components
Update 1 - Possible solution is FutureBuilder
I know that there's a possible solution with FutureBuilder but I think even Provider is apt for this use case.
Moreover is it an anti-pattern here ?
Also, please don't suggest another package for the same thing, if possible try limiting the solution to Provider or base libraries.
Update 2 - Not possible with FutureBuilder
FutureBuilder can't be used here because there's a delete button in the list tile and hence when the note gets deleted the note list won't get updated.
The issue is coming because of the getNotes function you are calling from build method. You are calling notifyListeners from that function. It again re-builds the widget and calls the build method again and this cycle continues.
You either need to set false to the listen property of provider, but it will break your functionality. To fix this, you have to move the getNotes call from build function to initState like following:
#override
void initState() {
super.initState();
postInit(() {
Provider.of<NotesProvider>(context).getNotes();
});
}
Implement postInit (Reference: Flutter Issue 29515):
extension StateExtension<T extends StatefulWidget> on State<T> {
Stream waitForStateLoading() async* {
while (!mounted) {
yield false;
}
yield true;
}
Future<void> postInit(VoidCallback action) async {
await for (var isLoaded in waitForStateLoading()) {}
action();
}
}
Note: Instead of writing the postInit code, you can also use after_init package for same purpose.
Several other posts discussing similar kind of issues:
How to correctly fetch APIs using Provider in Flutter
Using provider in fetching data onLoad
I got two collection in my firestore database and It's structure like,
Collection1 - movies
-movieTitle : String
-movieYear : String
-movieDirector : DocumentReference
.
.
etc
Collection2 - directors
-dirName: String
-dirImage: String
.
.
etc
I want to display movieTitle and dirName in a ListTile.
Here how I have tried to do so,
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _TestPageState();
}
}
class _TestPageState extends State<TestPage> {
DocumentSnapshot document;
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('movies').snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> movieSnapshot) {
if (!movieSnapshot.hasData) return const Text('Loading...');
final int messageCount = movieSnapshot.data.documents.length;
return ListView.builder(
itemCount: messageCount,
itemBuilder: (_, int index) {
document = movieSnapshot.data.documents[index];
return ListTile(
title: Text(document['movieTitle'] ?? 'title not retrieved'),
subtitle: Text(getValue(document["movieDirector"]) ??
'director not retrieved'),
);
},
);
},
),
);
}
String getValue(DocumentReference documentReference) {
String val;
documentReference.get().then((onData) {
val = onData.data["directorName"];
print(val);
});
return val;
}
}
Finally I couldn't be able to get the value on screen. What should I change in my implementation?
You need to learn about Futures and async/await so you can comfortably write such code.
The problem here is that getValue has to return immediately, but the directorName that it asks arrives sometime in the future. Therefore you simply can't get it right now.
Giving a FutureBuilder to the subtitle: is one of the options you can pursue.
Also you should consider caching the stream (and the future, if you implement it as per my suggestion above) so that you do not make unwanted requests. Right here I try to explain it in a presentation of mine: https://youtu.be/0gBsHLgCY6M?list=PL0bBHyAilexzBdvHookPcPZNMceciAaZf&t=1900
I've recently started using state management in flutter and have pretty much settled on BloC. However I do not use the bloc package or any similar dependency for it since my codebase is not that complex and I like writing it on my own. But I've come across an issue i just can't seem to get fixed. In summary, I have a stream that seems to just loose a certain event everytime i put it in the sink.
I've built an example app that is much simpler than my actual codebase, but still has this issue. The app consists of two pages with the first (main)page displaying a list of strings. When you click on one of the list-items, the second page will open up and the string/the item you clicked on will be displayed on this page.
Each of the two pages has an own BloC, but since the two pages need to be somewhat connected to get the selected item from the first to the second page, there is a third AppBloC which gets injected into the other two BloCs. It exposes a sink and a stream to send data between the other two BloCs.
The only third party package used in this example is kiwi (0.2.0) for dependency injection.
my main.dart is pretty simple and looks like this:
import 'package:flutter/material.dart';
import 'package:kiwi/kiwi.dart' as kw; //renamed to reduce confusion with flutter's own Container widget
import 'package:streams_bloc_test/first.dart';
import 'package:streams_bloc_test/second.dart';
import 'bloc.dart';
kw.Container get container => kw.Container(); //Container is a singleton used for dependency injection with Kiwi
void main() {
container.registerSingleton((c) => AppBloc()); //registering AppBloc as a singleton for dependency injection (will be injected into the other two blocs)
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final appBloc = container.resolve(); //injecting AppBloc here just to dispose it when the App gets closed
#override
void dispose() {
appBloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp( //basic MaterialApp with two routes
title: 'Streams Test',
theme: ThemeData.dark(),
initialRoute: "first",
routes: {
"first": (context) => FirstPage(),
"first/second": (context) => SecondPage(),
},
);
}
}
then there are the two pages:
first.dart:
import 'package:flutter/material.dart';
import 'package:streams_bloc_test/bloc.dart';
class FirstPage extends StatefulWidget { //First page that just displays a simple list of strings
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
final bloc = FirstBloc();
#override
void dispose() {
bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("FirstPage")),
body: StreamBuilder<List<String>>(
initialData: [],
stream: bloc.list,
builder: (context, snapshot) {
return ListView.builder( //displays list of strings from the stream
itemBuilder: (context, i){
return ListItem(
text: snapshot.data[i],
onTap: () { //list item got clicked
bloc.selectionClicked(i); //send selected item to second page
Navigator.pushNamed(context, "first/second"); //open up second page
},
);
},
itemCount: snapshot.data.length,
);
}),
);
}
}
class ListItem extends StatelessWidget { //simple widget to display a string in the list
final void Function() onTap;
final String text;
const ListItem({Key key, this.onTap, this.text}) : super(key: key);
#override
Widget build(BuildContext context) {
return InkWell(
child: Container(
padding: EdgeInsets.all(16.0),
child: Text(text),
),
onTap: onTap,
);
}
}
second.dart:
import 'package:flutter/material.dart';
import 'package:streams_bloc_test/bloc.dart';
class SecondPage extends StatefulWidget { //Second page that displays a selected item
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
final bloc = SecondBloc();
#override
void dispose() {
bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: StreamBuilder( //selected item is displayed as the AppBars title
stream: bloc.title,
initialData: "Nothing here :/", //displayed when the stream does not emit any event
builder: (context, snapshot) {
return Text(snapshot.data);
},
),
),
);
}
}
and finally here are my three BloCs:
bloc.dart:
import 'dart:async';
import 'package:kiwi/kiwi.dart' as kw;
abstract class Bloc{
void dispose();
}
class AppBloc extends Bloc{ //AppBloc for connecting the other two Blocs
final _selectionController = StreamController<String>(); //"connection" used for passing selected list items from first to second page
Stream<String> selected;
Sink<String> get select => _selectionController.sink;
AppBloc(){
selected = _selectionController.stream.asBroadcastStream(); //Broadcast stream needed if second page is opened/closed multiple times
}
#override
void dispose() {
_selectionController.close();
}
}
class FirstBloc extends Bloc { //Bloc for first Page (used for displaying a simple list)
final appBloc = kw.Container().resolve<AppBloc>(); //injected AppBloc
final listItems = ["this", "is", "a", "list"]; //example list items
final _listController = StreamController<List<String>>();
Stream<List<String>> get list => _listController.stream;
FirstBloc(){
_listController.add(listItems); //initially adding list items
}
selectionClicked(int index){ //called when a list item got clicked
final item = listItems[index]; //obtaining item
appBloc.select.add(item); //adding the item to the "connection" in AppBloc
print("item added: $item"); //debug print
}
#override
dispose(){
_listController.close();
}
}
class SecondBloc extends Bloc { //Bloc for second Page (used for displaying a single list item)
final appBloc = kw.Container().resolve<AppBloc>(); //injected AppBloc
final _titleController = StreamController<String>(); //selected item is displayed as the AppBar title
Stream<String> get title => _titleController.stream;
SecondBloc(){
awaitTitle(); //needs separate method because there are no async constructors
}
awaitTitle() async {
final title = await appBloc.selected.first; //wait until the "connection" spits out the selected item
print("recieved title: $title"); //debug print
_titleController.add(title); //adding the item as the title
}
#override
void dispose() {
_titleController.close();
}
}
The expected behavior would be, that everytime I click on one of the list-items, the second page would open up and display that item as its title. But that's not what is happening here.
Executing the above code will look like this. The first time when you click on a list item, everything works just as intended and the string "this" is set as the second page's title. But closing the page and doing so again, "Nothing here :/" (the default string/initial value of the StreamBuilder) gets displayed. The third time however, as you can see in the screencap, the app starts to hang because of an exception:
Unhandled Exception: Bad state: Cannot add event after closing
The exception occurrs in the BloC of the second page when trying to add the recieved string into the sink so it can be displayed as the AppBar's title:
awaitTitle() async {
final title = await appBloc.selected.first;
print("recieved title: $title");
_titleController.add(title); //<-- thats where the exception get's thrown
}
This seems kind of weird at first. The StreamController (_titleController) is only getting closed when the page is also closed (and the page has clearly not gotten closed yet). So why is this exception getting thrown?
So just for fun I uncommented the line where _titleController gets closed. It will probably create some memory leaks, but that's fine for debugging:
#override
void dispose() {
//_titleController.close();
}
Now that there are no more exceptions that will stop the app from executing, the following happens: The first time is the same as before (title gets displayed - expected behavior), but all the following times the default string gets displayed, not matter how often you try it. Now you may have noticed the two debug prints in bloc.dart. The first tells me when an event is added to the AppBloc's sink and the second one when the event is recieved. Here is the output:
//first time
item added: this
recieved title: this
//second time
item added: this
//third time
item added: this
recieved title: this
//all the following times are equal to the third time...
So as you can clearly see, the second time the event somehow got lost somewhere. This also explains the exception I was getting before. Since the title never got to the second page on the second try, the BloC was still waiting for an event to come through the stream. So when i clicked on the item the third time, the previous bloc was still active and recieved the event. Of course then the page and the StreamController were already closed, ergo the exception. So everytime the default string is displayed the following times is basically just because the previous page was still alive and caught the string...
So the part I can't seem to figure out is, where did that second event go? Did i miss something really trivial or get something wrong somewhere? I tested this on the stable channel (v1.7.8) as well as on the master channel (v1.8.2-pre.59) on multiple different android versions. I used dart 2.4.0.
You can try to use Rxdart's BehaviorSubject instead of StreamController in your main AppBloc
final _selectionController = BehaviorSubject<String>();
And your stream listener can be a just stream instead of a broadcast stream
selected = _selectionController.stream;
The reason I am suggesting this is because RxDart's BehaviorSubject makes sure it always emits the last stream at every point in time wherever it is being listened to.