Flutter update widgets using firebase remote config - firebase

I am working on a flutter app and I want to update the widgets of a page without pushing new updates on the play store. I have researched a bit on this topic and found that I can use firebase remote config. My idea is to use firebase remote config to fetch the widgets and show them on the app so that I don't need to publish frequent updates for small changes.
For Example, the below code is used to show a circular loading bar:
import 'package:flutter/material.dart';
class LoadingWidget extends StatelessWidget {
const LoadingWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: const CircularProgressIndicator(
color: Colors.black,
),
);
}
}
But now even for the smallest change, let it be changes in color, alignment, or size I need to push a new update to the play store and users need to install the new update in order to reflect the changes.
I want to store the flutter widget in firebase remote config (JSON format) then in the app, it will fetch the JSON data from remote config and show the new changes.

Once your RemoteConfig, you need do add key-value pair to match your needs:
key : value
color : black
Thenm fetch the value:
myRemoteColor = remoteConfig.getString('color')
Now, your myRemoteColor has a string with value black.

Since you already know that Remote Config only stores primitive values, the problem becomes how to encode and decode a Flutter widget into a string.
I doubt that is going to be simple though, as it'd mean that your application would need to compile the JSON/string into a binary. You'd essentially be implementing something akin to Flutter's Hot Reload for your remote clients, something that'd be pretty cool but is not a built-in feature.
More feasible is to control specific widgets that you already have in your code, as for example Rubens answered. In a recent sample app I used Remote Config to enable/disable a new feature (a process known as feature flagging) with:
class _MyHomePageState extends State<MyHomePage> {
final FirebaseRemoteConfig remoteConfig;
_MyHomePageState(this.remoteConfig);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: remoteConfig.getBool("chat_enabled") ? 3 : 2,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
const Tab(icon: Icon(Icons.games_outlined)),
const Tab(icon: Icon(Icons.swap_vert_circle_outlined)),
if (remoteConfig.getBool("chat_enabled")) const Tab(icon: Icon(Icons.chat_bubble_outlined)),
],
),
title: Text(widget.title),
),
body: TabBarView(
children: [
const MyGame(key: Key('game')),
const Leaderboard(key: Key('leaderboard')),
if (remoteConfig.getBool("chat_enabled")) const Messageboard(key: Key('messageboard')),
],
),
),
),
);
}

Related

How can I mix a statenotifier, with streamproviders that fetch 2 collections from Firebase to get constant update on a chat list

This is my first question here and I hope I’m not making it too complex.
So, I’m a junior programmer and I’ve start learning flutter, firebase and riverpod a couple of months ago and I’m stuck in a specific project.
For part of the app, what I need to do is something very similar to WhatsApp:
A screen with all the user chats,
This screen (or part of it) should update every time a chat gets a new message, showing the last message snippet and turning into bold if not read,
The shown chats should change between unarchived and archived, depending on a button in the UI.
Couldn’t have picked up an easier starting project, right? ;) (now every time I look at WhatsApp I say wow!)
Regarding firebase/firestore I’m fetching 2 different collections for this:
the sub-collection ‘chats’ within the ‘user_chats’ collection: where I get all the chat Ids plus it’s status (if this chat is archived and if the last message was read), for the current user,
the main ‘chats’ collection, where I have the main info of each chat.
At this moment I’m doing this:
In the chats_screen (UI) I’m fetching my chat provider: userChatsProvider
(note: this is just an example of the UI implementation. I have another implementation for it, but as long I get the chatName and lastMsgContent updated for each chat, perfect.)
class ChatsScreen extends ConsumerWidget {
const ChatsScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
(…)
return Scaffold(
appBar: (…) // not relevant for this question
body: Center(
child: Consumer(
builder: (BuildContext context, WidgetRef ref, Widget? child) {
return ref.watch(userChatsProvider).when(
loading: () => const CircularProgressIndicator(),
error: (err, st) => Center(child: Text(err.toString())),
data: (chatData) {
return Column(
children: [
// button to get archived chats
child: TextButton(
child: (…)
onPressed: () {}),
),
Expanded(
child: ListView.builder(
itemCount: chatData.length,
itemBuilder: (ctx, index) => Row(
children: [
Text (chatData[index].chatName!),
Text (chatData[index].lastMsgContent!),
]
),
),
),
]
);
}
);
}
)
)
);
}
}
In the chats_provider (provider) I’m fetching the 2 repository providers and joining them into a specific model I’ve created for this screen:
final userChatsProvider = FutureProvider.autoDispose<List<ChatScreenModel>>((ref) async {
const String userId = ‘XXX’; // this will be substituted by a dynamic variable with the current user Id
try {
final List<UserChat> userChats =
await ref.watch(userChatsRepositoryProvider).get(userId: userId);
final List<String> chatIdList = userChats.map<String>((e) => e.id).toList();
final List<Chat> chats =
await ref.watch(chatsRepositoryProvider).get(chatIds: chatIdList);
// ref.maintainState = true;
return toChatScreenModel(chats, userChats);
} on Exception catch (e) {
throw const CustomException();
}
});
In the repositories I’m fetching the firestorm collections I mentioned above. Here’s an example:
final userChatsRepositoryProvider =
Provider<UserChatsRepository>((ref) => UserChatsRepository(ref.read));
class UserChatsRepository {
final Reader _read;
const UserChatsRepository(this._read);
Future<List<UserChat>> get({required String userId}) async {
try {
final snap = await _read(firebaseFirestoreProvider)
.collection('users_chats/$userId/chats')
.get();
// Maybe this step is not necessary, but I’ve decided to transform the data into temporary models, before sending to provider
List<UserChat> userChats =
snap.docs.map((doc) => UserChat.fromJson(doc.data(), doc.id)).toList();
return userChats;
} on FirebaseException catch (e) {
throw CustomException(message: e.message);
}
}
}
And by the way, this is the model I’m sending to the UI:
class ChatScreenModel {
final String? id;
final String? chatName;
final String? chatImage;
final String? lastMsgContent;
final String? lastMsgDate;
final ChatType? chatType;
final bool? archived;
final bool? msgRead;
ChatScreenModel({
this.id,
this.chatName,
this.chatImage,
this.lastMsgContent,
this.lastMsgDate,
this.chatType,
this.archived,
this.msgRead,
});
Problems with this implementation:
I’m getting the user chats in the screen, but they don’t update since I’m not using a stream. So I get a snapshot, but it will only update if I leave and enter that chats_screen again. And it would be important to have it updating with a stream.
I’m showing all the chats, and not a filtered list with only the unarchived chats.
Also, related with the previous point, I still don’t have the archived button working, to only show the archived chats.
I’ve lost many, many hours trying to understand how I could implement a stream provider and a state notifier provider in this workflow.
Tried many combinations, but without success.
Can anyone help me understand how to do this?
Priority: transform these providers into stream providers (so it updates the UI constantly).
Nice to have: also include the archived/unarchived dynamic to filter the chats that appear and be able to switch between them.
Thanks a lot. :)

Flutter BLoC Architecture - Role(s)

This is a question mostly about the best practice, I'm new to flutter dev and to the BLoC architecture, to summarize I have an app that has an app_bloc which handles user authentication (firebase login), it's pretty simple, has two states Authenticated and Unauthenticated.
My app renders pages depending on whether user is authenticated or not, either login page or home page.
import 'package:authentication_repository/authentication_repository.dart';
import 'package:flow_builder/flow_builder.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:majstor/app/bloc/app_bloc.dart';
import 'package:majstor/app/routes/routes.dart';
import 'package:majstor/theme.dart';
class App extends StatelessWidget {
const App({
Key? key,
required AuthenticationRepository authenticationRepository,
}) : _authenticationRepository = authenticationRepository,
super(key: key);
final AuthenticationRepository _authenticationRepository;
#override
Widget build(BuildContext context) {
return RepositoryProvider.value(
value: _authenticationRepository,
child: BlocProvider(
create: (_) => AppBloc(
authenticationRepository: _authenticationRepository,
),
child: const AppView(),
),
);
}
}
class AppView extends StatelessWidget {
const AppView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
/// try context.select here maybe?
return MaterialApp(
home: FlowBuilder<AppStatus>(
state: context.select((AppBloc bloc) => bloc.state.status),
onGeneratePages: onGenerateAppViewPages,
),
);
}
}
import 'package:flutter/widgets.dart';
import 'package:majstor/app/app.dart';
import 'package:majstor/home/home.dart';
import 'package:majstor/login/login.dart';
List<Page> onGenerateAppViewPages(AppStatus state, List<Page<dynamic>> pages) {
switch (state) {
case AppStatus.authenticated:
return [HomePage.page()];
case AppStatus.unauthenticated:
default:
return [LoginPage.page()];
}
}
Now what I'm having a hard time wrapping my head around is this, in my home page case, I'd like to further 'split' my users into roles, there will be two roles stored on a Firebase collection with the user's corresponding id on the same document, and for each role a different page entirely should be built, I'm not sure where exactly would I do this role check, I could technically in the home page below, query the database by using the user id, and getting the role and then similarly as i did with the login and home, use a flowbuilder to generate different page depending on role? But is that a good practice? I kind of feel like I should be separating the database logic elsewhere, but as I'm new to this kind of development I don't know where I should be doing this to build a scalable app.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:majstor/app/app.dart';
import 'package:majstor/home/home.dart';
import 'package:majstor/utils/databaseservice.dart';
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
static Page page() => const MaterialPage<void>(child: HomePage());
#override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final user = context.select((AppBloc bloc) => bloc.state.user);
Database db = Database();
db.readData(user.id);
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
actions: <Widget>[
IconButton(
key: const Key('homePage_logout_iconButton'),
icon: const Icon(Icons.exit_to_app),
onPressed: () => context.read<AppBloc>().add(AppLogoutRequested()),
)
],
),
body: Align(
alignment: const Alignment(0, -1 / 3),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Avatar(photo: user.photo),
const SizedBox(height: 4),
Text(user.email ?? '', style: textTheme.headline6),
const SizedBox(height: 4),
Text(user.name ?? '', style: textTheme.headline5),
],
),
),
);
}
}
Do I query the database in the file above, and do the checks there or, is there a way to sort of separate my logic further? Should I be using BloCs for roles aswell? or should I be somehow integrating roles into my existing BloC? Hope I was clear enough with my explanation.
So I'm not a Flutter developer, but I might be able to offer some advice based on my general experience and some research I did for your question. And I'm assuming your concept of Bloc's align's somewhat with this.
I'd like to further 'split' my users into roles ... I'm not sure where
exactly would I do this role check
I would make a Bloc (logic class) that represents a users authentication and authorization state, which for the sake of discussion we'll call UserAuthBloc.
As your other Bloc's go doing stuff they can refer to the UserAuthBloc, whenever they need to make a decision based on the users authentication status or role.
Using BlocProvider to implement dependency injection is generally wise, and you can also use it to provide the UserAuthBloc to widgets that need to make decisions based on the users authentication status and role membership.
Do I query the database in the file above, and do the checks there or,
is there a way to sort of separate my logic further? Should I be using
BloCs for roles as well?
The database querying for user authentication and authorization (roles) should be done exclusively through the UserAuthBloc.
The checks are done at the Bloc level, at least as much as possible. If you are doing checks in the UI, then try and limit them to reusable widgets so that there's less code to change if you need to makes changes.
Keeping authentication and authorization (roles) in the same Bloc makes sense to me in most cases, because the roles depend on the user being authenticated.

Getting a specific String from List.generate with onTap

I am using WordPress REST API to fetch posts from a WordPress site with this piece of code with the help of wordpress_api package.
class ProvideTitle with ChangeNotifier {
List<String> Titles = [];
List<String> Descriptions = [];
List<String> Urls = [];
void ClearTitle() { //clears lists when called
Titles.clear();
Descriptions.clear();
Urls.clear();
}
void TitleFetcher(term) async { //takes term from query and adds parameters
final String searchTerm=term;
final api = WordPressAPI('https://wordpresswebsite.com');
WPResponse res = await api.posts.fetch(args: {
"search" : searchTerm,
"per_page" : "20",
"page" : "1"
});
for (final post in res.data){ //returns String to fill Lists below for each post found
Titles.add(post.title);
Descriptions.add(post.content);
Urls.add(post.link);
}
notifyListeners();
}
}
Everything is working as expected and class notifies all listeners with each List<//Strings> respectively.
Problem here is I build a list with List.generate using List<//String> Title in search bar recommendations with material_floating_search_bar, code looks like this.
class SearchPage extends StatefulWidget {
const SearchPage({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => SearchPageState();
}
class SearchPageState extends State<SearchPage> {//search bar from package mentioned
late String resultValue= "";
final controller = FloatingSearchBarController(); //controller for search bar
#override
Widget build(BuildContext context) {
return SafeArea(
child: Consumer<ProvideTitle>(builder: (context, provideTitle, child) {//Provider used for listening to ProvideTitle class
return FloatingSearchBar(
debounceDelay: const Duration(milliseconds: 165),
onQueryChanged: (query) async { //registers keyboard input
query != "" ? provideTitle.TitleFetcher(query) //sends input to TitleFetcher function inside ProvideTitle class
: provideTitle.ClearTitle(); //clears lists to stop showing fetced data with empty query(please feel free to recommend me a better way to do)
},
controller: controller,
hint: "Search",
backdropColor: Colors.transparent,
borderRadius: BorderRadius.circular(24),
physics: const BouncingScrollPhysics(),
builder: (context, _) => BuildBody(), //calls list builder as recommendations
body: resultValue != "" ? SearchResults(): //result body going to be called when some entry chosen from list
SizedBox());//page stays empty when there is no action
}),
);
}
Widget BuildBody() {
ProvideTitle model = Provider.of<ProvideTitle>(context); //ProvideTitle called for its lists as model
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
child: Column(
children:
List.generate(model.Titles.length, (index) => model.Titles[index].toString()) //list generated as long as Title[index] which is 20 max because we only call 20 post from api
.map((e) => ListTile(
onTap: () { // i want this onTap to register which tile I chosed
model.ClearTitle();//clears lists for next search
controller.close();//closes search bar recommendations
},
title: Text(e),
))
.toList(),
),
),
);
}
}
After that list generated based on search, I want to chose one of the tiles to build a widget that contains fetched content of that post.
I created a class for contents to be displayed based on the tile we've chosen earlier but everything is also okay with that class.
How can I effectively call my class to show tiles contents that I chosen earlier, preferably without further fetching to stop wasting server resources and my devices resources.
I am completely new at flutter and developing this app for not more than three weeks without any previous coding experience other than few basic java apps that works on console, so please please please feel free to correct me in any way rather it's about my problem or not. Much thanks in advance.
Also this is my first question on here so excuse if formatting is not adequate enough for site.
Using indexOf() successfully returns the int value of chosen tile.
List.generate(model.titles.length,
(index) => model.titles[index].toString())
.map((e) => ListTile(
onTap: () {
setState(() {});
controller.close();
model.resultValue = model.titles.indexOf(e); // we are returning the int value of chosen tile to another class here
model.parsData();
model.clearFetchedData();
},
title: Text(e),
))
.toList(),

Flutter - Provider working for Web app but when I run on Android it breaks?

I am creating a cross platform flutter app and started developing on the Web version. I have used provider throughout to pass various pieces of data around my app and thus far it works well.
However, when I run my app on Android, the same provider I have used to pull data from Firestore doesn't register any values. When I run on web, I get 2 when I print the length of my list of values whereas on Android, I get 0.
I appreciate there isn't a great deal of information here, but i'm wondering if anyone else has experienced the same issue? And if so, how did you resolve it? Thanks.
UPDATE - added code
Here is how I access the stream from firestore:
class OurDatabase {
final CollectionReference customCollection =
FirebaseFirestore.instance.collection('myCollection');
Stream<List<CustomClass>> get customitems {
return customCollection.snapshots().map(
(QuerySnapshot querySnapshot) => querySnapshot.docs
.map(
(document) => CustomClass.fromFirestore(document),
)
.toList(),
);
}
Here is my main.dart:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MultiProvider(
providers: [
StreamProvider<List<CustomClass>>.value(
value: OurDatabase().customitems,
initialData: <CustomClass>[],
),
],
child: MaterialApp(theme: OurTheme().buildTheme(), home: OurHomePage()),
),
);
}
I then access the custom list here:
class OurHeadlineList extends StatelessWidget {
#override
Widget build(BuildContext context) {
final List<CustomClass> items =
Provider.of<List<CustomClass>>(context);
print(items .length);
return Container();
}
}
I have swapped out provider for a stream builder and that works - I guess the problem lies with provider?
Like I mentioned previously, when I run on Chrome, the provider works perfectly. But when I run on Android emulator, it doesn't pick up any of the values. I am able to log into firebase through both platforms which confuses me even more. Thoughts?

FirebaseStorage with flutter

Im new to firebase and i wasnt able to figure out what kinda parameter i shud pass to the putfile in the
following code , and is there any tutorials or proper documentation for firebase with flutter?
Implemented code
import "dart:io";
import "package:flutter/material.dart";
import "package:firebase_storage/firebase_storage.dart";
import "package:flutter/widgets.dart";
class uploadApp extends StatefulWidget{
#override
State<StatefulWidget> createState() =>_uploadApp();
}
class _uploadApp extends State<uploadApp>{
var count=1;
final File file = File("images/ts2.jpg");
final FirebaseStorage _storage = FirebaseStorage(storageBucket:"gs://console.firebase.google.com/project/baby-name-4ef54/storage/baby-name-4ef54.appspot.com");
StorageUploadTask _uploadTask;
void firebasebackend(){
String filePath = "images/$count.jpg";
setState(() {
_uploadTask = _storage.ref().child(filePath).putFile(file);
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title:"upload",
home:Material(
child:Container(child:Center(
child:
Column(
children: <Widget>[
Container(
width: 300.0,
height: 300.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(300.0)),
image:DecorationImage(
image: AssetImage("images/ts2.jpg"),
fit: BoxFit.cover
)
),
),
Center(
child: RaisedButton(
child:Text("Upload"),
onPressed: (){
firebasebackend();
setState(){
count=count+1;
};
},
),
)
],
),
)
)
)
);
}
}
'package:firebase_storage/src/storage_reference.dart': Failed assertion: line 62 pos 12: 'file.existsSync()': is not true.
What should I do to just upload an image directly from a folder to the firebase storage?
According to the code of firebase_storage plugin:
/// Asynchronously uploads a file to the currently specified
/// [StorageReference], with an optional [metadata].
StorageUploadTask putFile(File file, [StorageMetadata metadata]) {
assert(file.existsSync());
final _StorageFileUploadTask task =
_StorageFileUploadTask._(file, _firebaseStorage, this, metadata);
task._start();
return task;
}
Takes a file as a parameter and a variable of type StorageMetadata as an optional parameter.
https://github.com/FirebaseExtended/flutterfire/blob/master/packages/firebase_storage/lib/src/storage_reference.dart#L61
https://api.dart.dev/stable/2.7.0/dart-io/File-class.html
From the error message you get, it looks like there is no file at File("images/ts2.jpg"); on your phone, which means the SDK can't find any data to upload to Cloud Storage.
In general I'd be suspicious of the relative path you're using here. The example that comes with the Firebase Storage SDK creates a file at an absolute path in the temp dir with final File file = await File('${systemTempDir.path}/foo$uuid.txt').create();, and I'd recommend using absolute paths on the file system too.
You will need to find the absolute path of the image you want to upload, either by browsing the file system of your device, or by taking the absolute path from the gallery or camera. For example, if you use a file picker, you can get the file with:
File file = await FilePicker.getFile();
This might seem very trivial, but Peter Haddad pointed out in the comments of his post
That's probably because there is no image with that path...
which is exactly the problem why I had this error.
My mistake was, that I assumed an error is thrown when the File object is created and a non-existent path was used to create this object.
However, this is not the case. Therefore, make sure to verify the File object refers to the correct path.

Resources