Recreate map in bloc pattern every time receive new location - flutter

I am trying to use bloc pattern in map application. Application when is started, user location is found first and then change the map center to user location.
When user moved i want to change marker on the map.This is my code:
class HomeFoursquareScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<LocationBloc, LocationState>(
builder: (context, locationState) {
if (locationState is LocationLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (locationState is LocationLoadedState) {
return _MyMap(locationState.location);
} else {
return const Text('oops...something went wrong');
}
},
);
}
}
class _MyMap extends StatelessWidget {
const _MyMap({Key key, #required this.location}) : super(key: key);
final Location location;
#override
Widget build(BuildContext context) {
return BlocBuilder<FourSquareBloc, FourSquareState>(
builder: (context, foursquareState) {
return FlutterMap(...);
}
);
},
)
But as you can see in my code every time my location is changed, that's mean when user is moving LocationLoadedState is triggered and _MyMap widget is called and FlutterMap is recreated !! I think it is not good for performance and it is not logical to create new instance of map continuously ! Is it right ? And What is right way?
I want to be fixed map but other thing like MapOptions or Marker get data when new data arrived.

Related

Provider to be initialized asynchronously from `initState()` but get `could not find the correct Provider`

I develop an ad app, with a message button on the detailed view.
When the user tap on it, the chats view (stateful widget) is pushed to the screen.
The initState() is there to call the asyncInitMessages() which asynchronously fetches the chats and related message from the distant database. The asyncInitMessages() belongs to the Chats class which extends ChangeNotifier.
/// A chat conversation
class Chats extends ChangeNotifier {
/// Internal, private state of the chat.
void asyncInitMessages(
{required ClassifiedAd ad,
required String watchingUserId,
required bool isOwner}) async {
// blah blah
}
}
The ClassifiedAdMessagesViewstateful widget class implementation is as follows (snipet):
#override
void initState() {
// == Fetch conversation and messages
asyncInitMessages();
}
void asyncInitMessages() async {
// === Update all messages
try {
Provider.of<Chats>(context, listen: false).asyncInitMessages(
ad: widget.ad,
watchingUserId: widget.watchingUser!.uid,
isOwner: _isOwner);
} catch (e) {
if (mounted) {
setState(() {
_error = "$e";
_ready = true;
});
}
}
}
#override
Widget build(BuildContext context) {
// <<<<<<<<<<< The exception fires at the Consumer line right below
return Consumer<Chats>(builder: (context, chats, child) {
return Scaffold(
// ... blah blah
Finally, when running ll that, I got the exception in the build at the Consumer line:
could not find the correct Provider<chats>
Help greatly appreciated.
[UPDATED]
Here is the main (very far up from the messages screen)
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
//if (Firebase.apps.isEmpty) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// } else {
// Firebase.app(); // if already initialized, use that one
// }
if (USE_DATABASE_EMULATOR) {
FirebaseDatabase.instance.useDatabaseEmulator(emulatorHost, emulatorPort);
}
runApp(RootRestorationScope(
restorationId: 'root',
child: ChangeNotifierProvider(
create: (context) => StateModel(),
child: const App())));
}
class App extends StatefulWidget {
const App({super.key});
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
#override
Widget build(BuildContext context) {
return PersistedAppState(
storage: const JsonFileStorage(),
child: MultiProvider(
providers: [
ChangeNotifierProvider<ThemeModel>.value(value: _themeModel),
//ChangeNotifierProvider<AuthModel>.value(value: _auth),
],
child: Consumer<ThemeModel>(
builder: (context, themeModel, child) => MaterialApp(
// blah blah
}
}
}
And the component just on top of the
/// Classified ad detail view
class ClassifiedAdDetailView extends StatefulWidget {
final User? watchingUser;
final ClassifiedAd ad;
const ClassifiedAdDetailView(
{Key? key, required this.watchingUser, required this.ad})
: super(key: key);
#override
State<ClassifiedAdDetailView> createState() => _ClassifiedAdDetailViewState();
}
class _ClassifiedAdDetailViewState extends State<ClassifiedAdDetailView>
with TickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => Chats(),
builder: ((context, child) => Scaffold(
// blah blah
ElevatedButton(
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ClassifiedAdMessagesView(
ad: ad,
watchingUser: widget.watchingUser)));
}),
Providers must be located in the widget tree above the widget where you want to use them with Consumer or Provider.of. When you push a new route with Navigator, it won't be add the pushed route below the widget from where you push, it will add it at the same level where home of MaterialApp is located.
(I think the error message you get also states that you can't access the providers between routes.)
In general the tree will look like this if you push some routes (check it with the Flutter Widget Inspector):
MaterialApp
home
widget1
widget2
widget21
widget22
page1
widget1
widget2
page2
page3
In your code you create the provider in ClassifiedAdDetailView and then push
ClassifiedAdMessagesView from this in the onPressed method. You won't be access this provider from ClassifiedAdMessagesView because the tree will be like (simplified):
MaterialApp
home
ClassifiedAdDetailView
ClassifiedAdMessagesView
The solution is to "lift the state up" and place the provider above every widget from where you need to access it. It can be a part of your existing Multiprovider above MaterialApp but if it is too far, you need to find a proper place that is above both ClassifiedAdDetailView and ClassifiedAdMessagesView.

Statefull widget with Provier rebuild scenario does not work in real life

I thought I understand Flutter well, but sometimes widget building does not happen as I imagine. Could someone explain to me why this approach does not work?
I have a stateful widget that contains a child. In this example, this child is called a Graph. The child starts out as a null; therefore the widget should be an empty container. When data arrives from the provider, it should become a real Graph. When updated data from the provider comes, the Graph widget should be rebuilt with the newest items.
When I try this in reality, I witness Graph changing from null to something, however, it stays always the same, even when the new data arrives.
I imagine this has something to do with immutability?
class Name extends StatefulWidget {
const Name({Key key}) : super(key: key);
#override
State<Name> createState() => _NameState();
}
class _NameState extends State<Name> {
#override
Widget build(BuildContext context) {
Graph currentGraph;
return Consumer<DatabaseProvider>(builder: (context, db, child) {
setNewModelIfNeeded(db);
return currentGraph ?? Container();
});
}
setNewModelIfNeeded(db) {
if(db.graph != currentGraph){
setState(() {
currentGraph = db.graph;
});
}
}
}
UPDATE
When constructing a new graph, adding a value "key" that is different from the previous will make the widget rebuild. E.g.:
currentGraph = Graph(copyValuesFrom: db.graph, key: db.graph.toString());
Use global variables. Cannot call setState during building process. It’s strict especially with Stack widget because Overlay needs all calculation of layouting information at first of building.
update: added sample code to the comment question.
class _NameState extends State<Name> {
Graph? _currentGraph;
#override
Widget build(BuildContext context) {
return Consumer<DatabaseProvider>(builder: (context, db, child) {
setNewModelIfNeeded(db);
return _currentGraph ?? Container();
});
}
setNewModelIfNeeded(db) {
if(db.graph != _currentGraph){
_currentGraph = db.graph;
}
}
}
If you want to pass variables that can change over time to your object, consider using ProxyProvider.

Delete an item in the source list from a flutter ListView

I'm starting to learn Flutter and I'm trying to write an application.
The application has a list of players in a ListView of SwitchListTile. This is working at the moment. I'm trying to add a function to delete one of the players from the lists.
class PlayersSwitchListTilesContainer extends StatelessWidget {
PlayersSwitchListTilesContainer({this.players});
final List<PlayerModel> players;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children : players.map<Widget>((PlayerModel p){
return PlayerSwtichListTile(singlePlayer: p);
}).toList()
),
);
}
}
class PlayerSwtichListTile extends StatefulWidget {
PlayerSwtichListTile({this.singlePlayer});
final PlayerModel singlePlayer;
void removePlayer()
{
// What goes here ???
print('Delete ' + singlePlayer.playerName);
}
#override
_PlayerSwtichListTileState createState() => new _PlayerSwtichListTileState(player: singlePlayer);
}
At the moment, when I try to delete a player it calls the correct code and prints the player's name. However, I'm struggling to see how to delete the player from the players list.
I'd be grateful for any pointers anyone has
From what I understand, to delete a player from the list of players you can do this but for this method, you need to provide the index number manually:
setState(() {
players.removeAt(index);
});
A better approach would be to use a listView.builder and add a button to the PlayerSwtichListTile which can receive the index from listView.builder so that whenever you click that button then that PlayerSwtichListTile player would get removed:
ListView.builder(
itemCount: players.length ?? 0,
itemBuilder: (context, index) {
return PlayerSwtichListTile(singlePlayer: p, index: index);}
)
class PlayerSwtichListTile extends StatefulWidget {
PlayerSwtichListTile({this.singlePlayer, this.index});
final int index;
final PlayerModel singlePlayer;
#override
_PlayerSwtichListTileState createState() => new _PlayerSwtichListTileState(player: singlePlayer);
}
class _PlayerSwtichListTile extends State<PlayerSwtichListTile > {
var player;
_PlayerSwtichListTile({this.player});
//call this function in ontap of that delete button
void removePlayer()
{
setState(() {
players.removeAt(widget.index);
});
}
#override
Widget build(BuildContext context) {
return Container();
}
}

How to get StreamProvider data in children with new routes in Flutter (Dart)

I am using the StreamProvider method to wrap my widgets with certain data, such as Auth (which is working anywhere in my app) from Firebase Auth. I want to do the same with a Firestore value but it only seems to work one level deep.
I have a database call that finds an employees profile once the auth check is done. When I try get the employee from my Home() widget with Provider.of(context) it works great:
This is my wrapper widget (which is my main file's home: widget)
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
print(user.uid);
// Return either home or authenticate widget
if (user == null) {
return Authenticate();
}
else {
return StreamProvider<Employee>.value(
value: DatabaseService().linkedEmployee(user.uid),
child: Home(),
);
}
}
}
The Database Service function from DatabaseService():
// Get Linked Employee
Stream<Employee> linkedEmployee(String uid) {
return employeesCollection.where("linkedUser", isEqualTo: uid).snapshots().map(_linkedEmployeeFromSnapShot);
}
Employee _linkedEmployeeFromSnapShot(QuerySnapshot snapshot) {
final doc = snapshot.documents[0];
return Employee(
eId: doc.data["eId"],
employeeCode: doc.data["employeeCode"],
fName: doc.data["fName"],
lName: doc.data["lName"],
docId: doc.documentID
);
}
I can access Provider.of<User>(context) from any widget anywhere in my tree. So why can't I do the same for Provider.of<Employee>(context) ?
When I try that in any widget other than Home() I get the error:
Error: Could not find the correct Provider above this Vehicles Widget
For example, in my widget Vehicles:
class Vehicles extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
final employee = Provider.of<Employee>(context);
...
The User Provider works fine, I can print it out, but the employee provider does not work.
Is it something to do with context? Thanks, any advice would be appreciated.
How I'm navigating to the Vehicles() widget from Home() with a raised button with this event :
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Vehicles())
);
},
Here is a more explained reply hence I think some encounter this issue and I also think it's a bit tricky to get the head around it, especially when you have rules in your Firestore that requires a user to be authorized to access the database.
But generally, you want to wrap providers (that you want to access around all of the app) around MaterialApp().
So I'll show you a simple example to easier understand it.
//The App() handles makes the providers globally accessible
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FirebaseAuthProviderLayer(
child: AuthorizedProviderLayer(
authorizedChild: MatApp(child: StartSwitch()),
unAuthorizedChild: MatApp(child: SignInScreen()),
),
);
}
}
//The MaterialApp Wrapped so that it not has to be rewritten
class MatApp extends StatelessWidget {
Widget child;
MatApp({this.child});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App',
home: child,
);
}
}
class FirebaseAuthProviderLayer extends StatelessWidget {
final Widget child;
FirebaseAuthProviderLayer({this.child});
#override
Widget build(BuildContext context) {
return StreamProvider<User>.value(
value: FirebaseAuth.instance.authStateChanges(),
child: child,
);
}
}
//And the layer that decides either or not we should attach all the providers that requires the user to be authorized.
class AuthorizedProviderLayer extends StatelessWidget {
Widget authorizedChild;
Widget unAuthorizedChild;
AuthorizedProviderLayer({this.unAuthorizedChild, this.authorizedChild});
User user;
final FirestoreService firestoreService =
FirestoreService(); //The Service made to access Firestore
#override
Widget build(BuildContext context) {
user = Provider.of<User>(context);
if (user is User)
return MultiProvider(
providers: [
StreamProvider<FirestoreUserData>.value(
value: firestoreService.streamUser(),
),
StreamProvider<AppSettings>.value(
value: firestoreService.streamSettings(),
initialData: null,
)
],
child: authorizedChild,
);
return unAuthorizedChild;
}
}

Flutter wait for signal from firestore bool

I have a question about Flutter and Firestore.
I want to wait until another app set the bool from Firestore "roomStart" to true, to open a view. If "roomStart" is false, it should wait until it is set to true and then start the if statement again.
class QuizPage extends StatefulWidget {
final Room room;
QuizPage(this.questions, this.room);
#override
_QuizPageState createState() => _QuizPageState(room);
}
class _QuizPageState extends State<QuizPage> {
final Room room;
_QuizPageState(this.room);
#override
Widget build(BuildContext context) {
if(room.roomStart) {
return MaterialApp(
home: Scaffold(
//code
);
} else {
// code: wait for boolean is set on true
);
}
}
}
enter image description here
The idea i had was to set a setState but i still lack the right approach, does anyone have an example or a hint?
I would be very grateful.
Using the Firebase SDK you can get a Stream of data for your Document (Room) by calling onSnapshot() with firebase_cloudstore. The Stream will always output the latest value from firebase. You don't necessarily have to have a StatefulWidget, instead you can use a StatelessWidget with a StreamBuilder and you can put your if logic inside of the StreamBuilders builder method.
Make sure to check that the snapshot hasData and show the appropriate widget.
You can use streambuilder and listen snapshot as
import 'package:flutter/material.dart';
class App extends StatelessWidget {
#override
Widget build(context) {
return StreamBuilder(
stream: Firestore.instance
.collection('roomCollectionName')
.document('roomId')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
);
} else {
if (snapshot.data['roomstart']) {
//true
return Container();
} else {
//false
return Container();
}
}
},
);
}
}