FirebaseAuth.AuthStateListener using LiveData. Is there scope for improvement in below implementation? - firebase

Is there scope for improvement in implementing Architecture Components
or in general considering:
Note: if you choose to use an AuthStateListener, make sure to unregister it before launching the FirebaseUI flow and re-register it after the flow returns. FirebaseUI performs auth operations internally which may trigger the listener before the flow is complete.
LiveData
public class FirebaseAuthLiveData extends LiveData<FirebaseUser> {
private FirebaseAuth firebaseAuth = FirebaseAuth.getInstance();
private FirebaseAuth.AuthStateListener authStateListener =
new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser firebaseUser = firebaseAuth.getCurrentUser();
setValue(firebaseUser);
}
};
#Override
protected void onActive() {
super.onActive();
firebaseAuth.addAuthStateListener(authStateListener);
}
#Override
protected void onInactive() {
super.onInactive();
firebaseAuth.removeAuthStateListener(authStateListener);
}
}
ViewModel
public class FirebaseAuthViewModel extends ViewModel {
private final FirebaseAuthLiveData firebaseAuthLiveData = new
FirebaseAuthLiveData();
public LiveData<FirebaseUser> getFirebaseAuthLiveData() {
return firebaseAuthLiveData; }
}
}
MainActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FirebaseAuthViewModel firebaseAuthViewModel =
ViewModelProviders.of(MainActivity.this).get(FirebaseAuthViewModel.class);
firebaseUserLiveData = firebaseAuthViewModel.getFirebaseAuthLiveData();
firebaseUserLiveData.observe(MainActivity.this, new Observer<FirebaseUser>() {
#Override
public void onChanged(#Nullable FirebaseUser firebaseUser) {
if (firebaseUser == null) {
final Intent intent = AuthUI.getInstance().createSignInIntentBuilder()
.setAvailableProviders(Collections.singletonList(
new AuthUI.IdpConfig.Builder(AuthUI.GOOGLE_PROVIDER).build())
).build();
startActivityForResult(intent, SIGN_IN);
} else {
updateUI(firebaseUser);
}
}
});
}

You're almost there. The single problem that you have is the use of the "FirebaseUser" object inside the activity, which breaks the MVVM architecture pattern, where is said that the activity should know nothing about its data source.
So the simplest and cleanest solution might be using a LiveData class:
class AuthLiveData(
private val auth: FirebaseAuth
): LiveData<Boolean>(), FirebaseAuth.AuthStateListener {
override fun onAuthStateChanged(auth: FirebaseAuth) {
value = auth.currentUser == null
}
override fun onActive() {
super.onActive()
auth.addAuthStateListener(this)
}
override fun onInactive() {
super.onInactive()
auth.removeAuthStateListener(this)
}
}
And a Repository class:
class MyRepository {
private val auth = FirebaseAuth.getInstance()
fun getFirebaseAuthState(): AuthLiveData {
return AuthLiveData(auth)
}
}
Now in the ViewModel class, we can simply:
class MyViewModel: ViewModel() {
val repository = MyRepository()
fun getAuthState(): LiveData<Boolean> {
return repository.getFirebaseAuthState()
}
}
In the end, in the activity we can observe the auth state changes like this:
viewModel.getAuthState().observe(this, { isUserSignedOut ->
if (isUserSignedOut) {
//Update the UI
}
})
This means that we'll always know when the user is signed in or not, without knowing which is the back-end.

Related

FirebaseAuth.getInstance().getCurrentUser().getUid() always pointing to same ID

I'm trying to make a feature in my Android studio app which allows users to book appointment with a fixed list of doctors through Firebase realtime database. The problem is that FirebaseAuth.getInstance().getCurrentUser().getUid() is always pointing to same ID and so instead of new appointment details being added via children nodes in the parent node, the existing details in children nodes are being overwritten. Here's my code-
DoctorList.java-
public class DoctorList extends AppCompatActivity {
TextView doc1,doc2,doc3,doc4;
FirebaseAuth mAuth;
DatabaseReference patUser;
ProgressDialog loader;
FirebaseDatabase database= FirebaseDatabase.getInstance();
String Date;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_doctor_list);
Intent intent = getIntent();
// receive the value by getStringExtra() method
// and key must be same which is send by first
// activity
String email = intent.getStringExtra("message_key");//patient email
doc1=findViewById(R.id.doc1);
doc2=findViewById(R.id.doc2);
doc3=findViewById(R.id.doc3);
doc4=findViewById(R.id.doc4);
loader = new ProgressDialog(this);
mAuth = FirebaseAuth.getInstance();
FirebaseUser user= mAuth.getInstance().getCurrentUser();
patUser= database.getReference().child("Patient Appointments");
doc1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
loader.setMessage("Please wait....");
loader.setCanceledOnTouchOutside(false);
loader.show();
Intent intent= new Intent( DoctorList.this, Book.class);
intent.putExtra("message_key1", "elise#doc.com");
intent.putExtra("message_key2", email);
startActivity(intent);
// create the get Intent object
Intent intent1 = getIntent();
// receive the value by getStringExtra() method
// and key must be same which is send by first
// activity
Date = intent1.getStringExtra("message");
//Toast.makeText(PatientPage.this, str, Toast.LENGTH_SHORT).show();
String currentUserId = mAuth.getCurrentUser().getUid();
patUser= database.getReference().child("Patient Appointments").child(currentUserId);
//HashMap userInfo = new HashMap();
HashMap userInfo = new HashMap();
userInfo.put("Date",Date);
userInfo.put("Patient",email);
userInfo.put("Doctor","Dr.Elise Heather");
userInfo.put("Phone","5925866");
userInfo.put("Status","Pending");
patUser.updateChildren(userInfo).addOnCompleteListener(new OnCompleteListener() {
#Override
public void onComplete(#NonNull Task task) {
if (task.isSuccessful()){
Toast.makeText(DoctorList.this, "Little more to go....", Toast.LENGTH_SHORT).show();
}
else{
Toast.makeText(DoctorList.this, task.getException().toString(), Toast.LENGTH_SHORT).show();
}
}
});
loader.dismiss();
//Intent intent2= new Intent( DoctorList.this, PatientPage.class);
//startActivity(intent2);
}
});
}
}
Book.java-
public class Book extends AppCompatActivity {
TextView selectedDate;
Button calenderButton,ok;
FirebaseAuth mAuth;
DatabaseReference docUser;
ProgressDialog loader;
FirebaseDatabase database= FirebaseDatabase.getInstance();
String Date;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book);
Intent intent = getIntent();
// receive the value by getStringExtra() method
// and key must be same which is send by first
// activity
String docEmail = intent.getStringExtra("message_key1");
String patEmail = intent.getStringExtra("message_key2");
selectedDate=findViewById(R.id.text);
calenderButton=findViewById(R.id.calender);
ok=findViewById(R.id.ok);
loader = new ProgressDialog(this);
mAuth = FirebaseAuth.getInstance();
FirebaseUser user= mAuth.getInstance().getCurrentUser();
docUser= database.getReference().child("Doctor Schedule");
MaterialDatePicker materialDatePicker=MaterialDatePicker.Builder.datePicker().
setTitleText("Select date").setSelection(MaterialDatePicker.todayInUtcMilliseconds()).build();
calenderButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
materialDatePicker.show(getSupportFragmentManager(),"Tag_Picker");
materialDatePicker.addOnPositiveButtonClickListener(new MaterialPickerOnPositiveButtonClickListener() {
#Override
public void onPositiveButtonClick(Object selection) {
selectedDate.setText(materialDatePicker.getHeaderText());
Date=materialDatePicker.getHeaderText();
}
});
}
});
ok.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (Date != null) {
String currentUserId = mAuth.getCurrentUser().getUid();
docUser = database.getReference().child("Doctor Schedule").child(currentUserId);
//HashMap userInfo = new HashMap();
HashMap userInfo = new HashMap();
userInfo.put("Date", Date);
userInfo.put("Patient", patEmail);
userInfo.put("Doctor", docEmail);
userInfo.put("Status", "Pending");
docUser.updateChildren(userInfo).addOnCompleteListener(new OnCompleteListener() {
#Override
public void onComplete(#NonNull Task task) {
if (task.isSuccessful()) {
Toast.makeText(Book.this, "Appointment booked", Toast.LENGTH_SHORT).show();
Intent intent= new Intent( Book.this, DoctorList.class);
intent.putExtra("message", Date);
startActivity(intent);
} else {
Toast.makeText(Book.this, task.getException().toString(), Toast.LENGTH_SHORT).show();
}
}
});
}
else {
Toast.makeText(Book.this, "Select date!", Toast.LENGTH_SHORT).show();
}
}
});
}
}
Suppose a user with email athy#gmail.com books appointment and in Firebase database there's already details of booking by another user nivi#gm.com . Then the details of the latter user in the parent node (ID) gets overwritten by user athy#gmail.com and it looks like this. I don't think I've written code for overwriting instead of updating since it works fine in other Android studio projects, so I'm guessing FirebaseAuth.getInstance().getCurrentUser().getUid() is the one always pointing to same ID which I've circled in the image.
How do I fix this?

Firebase ApiFuture to CompletableFuture in a reactive stack

I am using the Firebase Admin Java SDK in a server side Spring WebFlux environment. The Firebase SDK provides two methods for each operation. A synchronous ".doOperation()" method which returns a result and a ".doOperationAsync()" method which returns an instance of ApiFuture.
The Javadoc for ApiFuture can be found here.
ApiFuture extends from java.util.concurrent.Future, which is no longer supported by Mono.fromFuture().
Is there a way to convert Googles ApiFuture into a Mono?
It's possible to convert ApiFuture to CompletableFuture, which can be used with Mono.fromFuture:
internal class ApiCompletableFuture<V>(
private val future: ApiFuture<V>,
executor: Executor = directExecutor(),
) : CompletableFuture<V>(), ApiFutureCallback<V> {
init {
ApiFutures.addCallback(future, this, executor)
}
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
future.cancel(mayInterruptIfRunning)
return super.cancel(mayInterruptIfRunning)
}
override fun onSuccess(result: V) {
complete(result)
}
override fun onFailure(t: Throwable) {
completeExceptionally(t)
}
}
Some utility methods for you
public static <T> CompletableFuture<T> toCompletableFuture(ApiFuture<T> apiFuture) {
final CompletableFuture<T> cf = new CompletableFuture<>();
ApiFutures.addCallback(apiFuture,
new ApiFutureCallback<T>() {
#Override
public void onFailure(Throwable t) {
cf.completeExceptionally(t);
}
#Override
public void onSuccess(T result) {
cf.complete(result);
}
},
MoreExecutors.directExecutor());
return cf;
}
public static <T> Mono<T> toMono(ApiFuture<T> apiFuture) {
return Mono.create(sink -> ApiFutures.addCallback(apiFuture,
new ApiFutureCallback<T>() {
#Override
public void onFailure(Throwable t) {
sink.error(t);
}
#Override
public void onSuccess(T result) {
sink.success(result);
}
},
MoreExecutors.directExecutor()));
}

Xamarin Forms:Prism:Android:MainActivity: Click on Push Notifications: PushAsync not supported globally on Android, please use a NavigationPage

I am trying to implement a basic push notification example using
Xamarin Forms with Prism MVVM, Azure & FCM.
I am receiving notification, but couldn't navigate to a specific page when clicked on the notification.
Trying basic functionality when the app is running or in the background (not closed).
It's throwing an exception "PushAsync not supported globally on Android, please use a NavigationPage" at
ExploreXam.App.Current.MainPage.Navigation.PushAsync(page);
[Activity(LaunchMode = LaunchMode.SingleTask, MainLauncher = true]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
internal static readonly string CHANNEL_ID = "explore_xamarin";
internal static readonly int NOTIFICATION_ID = 1029;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
CreateNotificationChannel();
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
Intent = intent;
NotificationClickedOn(intent);
}
private void NotificationClickedOn(Intent intent)
{
if (intent.Action == ExploreXamFirebaseMessagingService.ExploreXamNotification && intent.HasExtra("XamId"))
{
var page = new Xamarin.Forms.NavigationPage(new SpecificPage());
Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(page);
ExploreXam.App.Current.MainPage.Navigation.PushAsync(page);
}
}
}
public partial class App : PrismApplication
{
public bool navigating;
public App(IPlatformInitializer initializer = null, bool shallNavigate=false) : base(initializer)
{
navigating = shallNavigate;
}
protected async override void OnInitialized()
{
BlobCache.ApplicationName = "ExploreXam";
InitializeComponent();
FlowListView.Init();
//await NavigationService.NavigateAsync("LoginPage");
await NavigationService.NavigateAsync("NavigationPage/LoginPage");
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//mapping
}
}
Any idea that would help out, please?
You could access Prims's NavigationService instance to achieve what you're trying to do. But, it's a protected property. So, first you'd have to expose it through your App class as below :
public new INavigationService NavigationService => base.NavigationService;
Now, you can access the NavigationService from anywhere in your app by simply referencing it through your App as below:
(Xamarin.Forms.Application.Current as App).NavigationService.NavigateAsync("your/page/path");
So, your App class would look something like this:
public partial class App : PrismApplication
{
public new INavigationService NavigationService => base.NavigationService;
public bool navigating;
public App(IPlatformInitializer initializer = null, bool shallNavigate=false) : base(initializer)
{
navigating = shallNavigate;
}
protected async override void OnInitialized()
{
BlobCache.ApplicationName = "ExploreXam";
InitializeComponent();
FlowListView.Init();
//await NavigationService.NavigateAsync("LoginPage");
await NavigationService.NavigateAsync("NavigationPage/LoginPage");
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//mapping
}
}
And your NotificationClickOn function would become something like :
private async void NotificationClickedOn(Intent intent)
{
if (intent.Action == ExploreXamFirebaseMessagingService.ExploreXamNotification && intent.HasExtra("XamId"))
{
var navigationService = (Xamarin.Forms.Application.Current as ContosoCookbook.App).NavigationService;
await navigationService.NavigateAsync("YourNavigationPage/SpecificPage");
}
}
The reason this is happening is because your Application.Current.MainPage is not a Navigation page but a ContentPage (i assume)
Wrap your initial MainPage in a NavigationPage as show below and it should work
In your App.xaml.cs
MainPage= new NavigationPage(new FirstPage());
I agree with #chaosifier. Create a public INavigationService in your App.xaml.cs file and then in the OnInitialized() method make the public property = the the base.NavigationService;
public INavigationService PrismNavigation { get; private set; }
protected override async void OnInitialized()
{
InitializeComponent();
PrismNavigation = base.NavigationService;
}
Then from the MainActivity.cs file you can navigate using something like this
(Xamarin.Forms.Application.Current as App).PrismNavigation.NavigateAsync(nameof(ShowAlertsDetailPage));
I hope this helps.

Subscriber only called once in Rx android and Firebase Realtime-Database Implementation

I am new to android development.
Currently I am implementing Firebase Realtime-Database with Rx android.
Here the Rx Android is used to listen to any changes happened in a particular child node inside the Realtime-Database, retrieve a List of Java class object in it and then return the List so that it can be used by another class.
Below are my code snippets.
1) Here is a class that do basic Database operation such as read, write, update and delete, right now I'm only showing the read operation.
public class FirebaseDatabaseLayer {
private DatabaseReference databaseReference = FirebaseDatabase
.getInstance().getReference();
private List<TodoComponentFirebase> todoComponentFirebases = new ArrayList<>();
private FirebaseUser user;
public Observable<List<TodoComponentFirebase>> readModelFirebase() {
return Observable.create(new Observable.OnSubscribe<List<TodoComponentFirebase>>() {
#Override
public void call(final Subscriber<? super List<TodoComponentFirebase>> subscriber) {
user = FirebaseAuth.getInstance().getCurrentUser();
databaseReference.child(user.getUid())
.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
for (DataSnapshot todoComponentSnapshot: dataSnapshot.getChildren()) {
TodoComponentFirebase todoComponentFirebase = todoComponentSnapshot.getValue(TodoComponentFirebase.class);
todoComponentFirebases.add(todoComponentFirebase);
}
subscriber.onNext(todoComponentFirebases);
subscriber.onCompleted();
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
for (DataSnapshot todoComponentSnapshot: dataSnapshot.getChildren()) {
TodoComponentFirebase todoComponentFirebase = todoComponentSnapshot.getValue(TodoComponentFirebase.class);
todoComponentFirebases.add(todoComponentFirebase);
}
subscriber.onNext(todoComponentFirebases);
subscriber.onCompleted();
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
});
}
2) Here is class which has a subscriber that listening to the Observable defined in the class in number 1) inside readModelFirebase method
public class Presenter {
private FirebaseDatabaseLayer firebaseDatabaseLayer;
private Subscription readSubscriber = null;
public void readFirebaseModel() {
readSubscriber = firebaseDatabaseLayer
.readModelFirebase()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<TodoComponentFirebase>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext (List<TodoComponentFirebase> todoComponentFirebases) {
mainView.getFirebaseData(todoComponentFirebases);
}
});
}
}
Here the 2) class pass the List of TodoComponentFirebase to the parameter of mainView.getFirebaseData method (I'm not showing concrete implementation of this method here). The idea here is that the mainView.getFirebaseData method will get a List of TodoComponentObject saved in Firebase Database everytime there's change happened in the respective child node.
MainView class is an AppCompatActivity class, the readFirebaseMethod() inside Presenter class is being called in onCreate method of the MainView class, this serve as subscription initialisation.
At the first time initialization of MainView class, the onNext inside readFirebaseModel() is executed, the data being retrieved seamlessly.
However after that there's no execution on that onNext method even though there's changes occur in Child Node which is defined in the 1) class.
I don't understand why the subscription is not working even though there's changes happen in the child node, it only worked the time it is being initialised. Is there anything missed in my Rx usage ?
Thanks
try remove the
subscriber.onCompleted();
I think when you call the onCompleted() subscriber will end up and not called again after any child event on firebase.

JSF custom panel with button - action not invoked

I have built a custom component button, but somehow the action is not invoked. When debugging the getAction-Method within the component and invoking the supplied MethodeExpression the Bean-Method is called as expected. But due to some reason, the Expression is not invoked when pressing the button in the browser.
Is there some kind of additional Interface necessary to pass the action to the embedded button-component?
Any help is very appreciated since I am stuck at this issue for some days now
MyClass:
public class MyClass extends UIPanel implements SystemEventListener
{
private UIForm form;
private HtmlCommandButton buttonOk;
public MyClass()
{
FacesContext context = getFacesContext();
UIViewRoot root = context.getViewRoot();
root.subscribeToViewEvent(PostAddToViewEvent.class, this);
}
#Override
public void processEvent(SystemEvent event)
{
this.form = new UIForm();
this.buttonOk = new HtmlCommandButton();
this.buttonOk.setId("okButtonId");
this.buttonOk.setActionExpression(getAction());
this.buttonOk.setValue("OK");
this.form.getChildren().add(this.buttonOk);
getChildren().add(this.form);
}
private enum PropertyKeys
{
action, text, titel
}
public MethodExpression getAction()
{
return (MethodExpression) getStateHelper().eval(PropertyKeys.action);
}
public void setAction(MethodExpression actionExpression)
{
getStateHelper().put(PropertyKeys.action, actionExpression);
}
public String getText()
{
return (String) getStateHelper().eval(PropertyKeys.text);
}
public void setText(String text)
{
getStateHelper().put(PropertyKeys.text, text);
}
public String getTitel()
{
return (String) getStateHelper().eval(PropertyKeys.titel);
}
public void setTitel(String titel)
{
getStateHelper().put(PropertyKeys.titel, titel);
}
#Override
public void encodeAll(FacesContext context) throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement(HTML.DIV_ELEM, this);
writer.writeText(getText(), null);
this.form.encodeAll(context);
writer.endElement(HTML.DIV_ELEM);
}
#Override
public void encodeChildren(FacesContext context) throws IOException
{
}
#Override
public boolean isListenerForSource(Object source)
{
return (source instanceof MyClass);
}
}
MyClassHandler:
public class MyClassHandler extends ComponentHandler
{
public MyClassHandler(ComponentConfig config)
{
super(config);
}
#SuppressWarnings("rawtypes")
#Override
protected MetaRuleset createMetaRuleset(Class type)
{
return super.createMetaRuleset(type).addRule(new MethodRule("action", String.class, new Class[] { ActionEvent.class }));
}
}
myView Method:
...
public String myMethod()
{
System.err.println("myMethod");
return "/some/path/yadayada.xhtml";
}
...
MyView.xhtml
<myTag action="#{myView.myMethod}" id="id1" titel="bla" text="bleh" />
Exdending UICommand is enough, since you only want one action to be executed.
You have to provide two additional MethodExpressions via the tag-attributes and within the decode-method you can check which button has been pressed and redirect the particular MethodExpression to the standard-action provided by UICommand. This way, you dont have to worry about the legacy-interface ActionSource, or how Events are broadcasted.
public void decode(FacesContext contex)
{
Map<String,String> map = context.getExternalContext.getRequestParameterMap();
// your rendered buttons need a name you check for
final boolean okPressed = map.containsKey( getClientId + ":ok" );
final boolean cancelPressed = map.containsKey( getClientId + ":cancel" );
if(okPressed || cancelPressed)
{
MethodExpression exp = null;
if(okPressed)
{
exp = getActionOk();
}
else
{
exp = getActionCancel();
}
// redirect to standard action
setActionExpression(exp);
queueEvent(new ActionEvent(this));
}
}
In order to make use of of this you need two attributes (actionOk and actionCancel) which use Method Expressions (setter and getter). Those have to be configured by a ComponentHandler as you did for the action-attribute.

Resources