(I'd just like to say I have a workaround for this using the EventAggregator - as seen at the bottom of the post - but I'd like to get to the bottom of why it is not working the way I have it currently set up).
I have a Xamarin Forms application in which I am using Prism (Version 8) for Xamarin Forms (Version 4.8).
In this application I am using Modules and Regions for Forms.
My main application has a MainPage (in a NavigationPage) in which I have a Prism Region into which Views are injected.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestPrismModules.Views.MainPage"
xmlns:prism="http://prismlibrary.com"
prism:ViewModelLocator.AutowireViewModel="True"
Title="{Binding Title}">
<StackLayout Orientation="Vertical">
<FlexLayout prism:RegionManager.RegionName="FlexRegion"/>
</StackLayout>
</ContentPage>
I have a Module (AuthModule) which handles Authentication and Logging in via an AuthenticationService.
This Module also contains a LoginPage with equivelant LoginPageViewModel. The Logout method in the AuthenticationService does the logging out and does a NavigateAsync to the LoginPage
public class AuthenticationService : IAuthenticationService
{
private readonly INavigationService _navigationService;
public AuthenticationService(INavigationService navigationService)
{
_navigationService = navigationService;
}
public void Logout()
{
// logging out code goes here
_navigationService.NavigateAsync("/LoginPage");
}
}
This is the AuthModule Module code:
public class AuthModuleModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<IAuthenticationService, AuthenticationService>();
containerRegistry.RegisterRegionServices();
containerRegistry.RegisterForNavigation<LoginPage, LoginPageViewModel>();
}
}
Upon launching the application, the first page navigated to is the LoginPage (reminder - in the AuthModule). So far so good.
I have a requirement that if a user deactivates the application momentarily at all that once it resumes or is reactivated the user is logged out and has to log in again. To this end I override the OnResume method, resolve an instance of my AuthenticationService and call the Logout method therein.
public partial class App
{
public App(IPlatformInitializer initializer)
: base(initializer)
{
}
protected override async void OnInitialized()
{
InitializeComponent();
var ea = Container.Resolve<IEventAggregator>();
ea.GetEvent<LoggedInEvent>().Subscribe(OnUserAuthenticated);
await NavigationService.NavigateAsync("/LoginPage",null,true,true);
}
private void OnUserAuthenticated()
{
NavigationService.NavigateAsync("/NavigationPage/MainPage");
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterRegionServices();
containerRegistry.RegisterSingleton<IAppInfo, AppInfoImplementation>();
containerRegistry.RegisterForNavigation<NavigationPage>();
containerRegistry.RegisterForNavigation<MainPage, MainPageViewModel>();
}
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
base.ConfigureModuleCatalog(moduleCatalog);
moduleCatalog.AddModule<TestModule1Module>();
moduleCatalog.AddModule<AuthModuleModule>();
}
// Want to force user to login when the app resumes
protected override void OnResume()
{
base.OnResume();
// Logout and redirect to Login Page
var authService = Container.Resolve<IAuthenticationService>();
authService.Logout();
}
}
This ALL works fine and dandy - until the user tries to log in again and they get the dreaded "Region with the given name is already registered
Navigating to the LoginPage directly from the main application (rather than through the AuthenticationService), and back to the MainPage after logging back in, works fine and I don't get the exception about the Region already being registered. Knowing that, I have a workaround where I use the EventAggregator to Subscribe to a LoggedOut event (in the App.xaml.cs) and publish that event from the AuthenticationService and call the NavigateAsync from the subscription.
I suspect I might get suggestions on using Scoped regions or some such but I'm not sure how to go about that.
I have a sample project available here to test if it helps: https://github.com/tomaswinston/TestPrismModulesAuth
I have a stateful service that stores a bunch of data about my users that is stored in a reliable dictionary and obviously also retrieves it from there too.
However, I also have a SQL database that used to store this info. On initialization of a new stateful service instance, I need to migrate that info from my SQL database into the new reliable storage mechanism. From that point on, the stateful service is the source of truth. Ideally, I'd like to delay availability of my stateful service until this initialization process is completed.
Are there any suggestions on an approach for how to do this?
Something like does will do the trick:
public interface IStateful1 : IService
{
Task MyMethod();
}
internal sealed class Stateful1 : StatefulService, IStateful1
{
private bool isReady = false;
public Stateful1(StatefulServiceContext context)
: base(context)
{ }
public Task MyMethod()
{
if(!isReady)
throw new NotImplementedException(); // Probably throw or return something more meaningful :-)
return Task.CompletedTask; // Do your thing here
}
protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
{
return new ServiceReplicaListener[0];
}
protected override async Task RunAsync(CancellationToken cancellationToken)
{
await Task.Run(() => {
// Simulation of some work
Thread.Sleep((int)TimeSpan.FromMinutes(5).TotalMilliseconds);
});
isReady = true;
}
}
In this setup the import from the DB into the reliable collection is done in the RunAsync method.
Unfortunately, AFAIK, there is not way to plug in the communication listeners at a later time. That would make things way easier.
If CreateServiceReplicaListeners would be an async operation we could await the initialization task here, but we can't right now. Using .Wait() is not going to work as it will report that the instance is taking to long to get running and will mark the instance as unhealthy.
A complete overview of the lifecycle of a service can be found in the docs
I am not sure if I got you right. But based on your comment I would suggest the following solution for returning the 'Not ready' response during the migration.
public interface IMigrationService
{
bool IsDone();
}
public class MigrationService : IMigrationService
{
private bool migrating = tu;
public bool BeginMigration()
{
this.migrating = true;
}
public bool EndMigration()
{
this.migrating = false;
}
public bool IsDone()
{
return this.migrating;
}
}
// WebHost startup class
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Register a middle-ware that would short circuit responses
// while migration is in progress.
app.Use(
async (context, next) =>
{
var migrationService =
context.RequestServices.GetService<IMigrationService>();
if (!migrationService.IsDone())
{
/* short circuit the response with approriate error message */
}
await next();
});
app.UseMvc();
}
}
public class Stateful : StatefulService
{
private readonly IMigrationService migrationService;
public Stateful(StatefulServiceContext context)
: base(context)
{
this.migrationService = new MigrationService();
}
protected override IEnumerable<ServiceReplicaListener>
CreateServiceReplicaListeners()
{
/*
Create a listener here with WebHostBuilder
Use Startup class with the middle-ware defined and
add configure services -> .ConfigureServices()
with services.AddSingleton<IMigrationService>(this.migrationService)
*/
}
protected override async Task
RunAsync(CancellationToken cancellationToken)
{
this.migrationService.StartMigration();
/* Migration code */
this.migrationService.EndMigration();
}
}
This would allow you to roll-out a new version of the service that would short circuit all requests with appropriate error message while the migration is in progress.
Hope this helps.
When I navigate from page A to page B, I need to remove page A.
How can I do this with Prism's navigation service in Xamarin Forms?
There are a few scenarios that people run into on this one.
As a common example say you have a LoginPage, and once the user successfully logs in you want to Navigate to the MainPage. Your code might look something like the following:
public class App : PrismApplication
{
protected override async void OnInitialized()
{
await NavigationService.NavigateAsync("LoginPage");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<LoginPage>();
Container.RegisterTypeForNavigation<MainPage>();
}
}
public class LoginPageViewModel : BindableBase
{
public DelegateCommand LoginCommand { get; }
private async void OnLoginCommandExecuted()
{
// Do some validation
// Notice the Absolute URI which will reset the navigation stack
// to start with MainPage
await _navigationService.NavigateAsync("/MainPage");
}
}
Now if what you're looking for is some flow where your navigation stack looks like MainPage/ViewA and what you want is MainPage/ViewB and you don't want to reinitialize MainPage, then this is something that we are currently evaluating and wanting to improve this so you could do something like _navigationService.NavigateAsync("../ViewB"). In the mean time what I might suggest is something like this:
public class ViewAViewModel : BindableBase
{
public DelegateCommand ViewBCommand { get; }
private async void OnViewBCommandExecuted()
{
var parameters = new NavigationParameters
{
{ "navigateTo", "ViewB" }
};
await _navigationService.GoBackAsync(parameters);
}
}
public class MainPageViewModel : BindableBase, INavigatedAware
{
public async void OnNavigatingTo(NavigationParameters parameters)
{
if(parameters. GetNavigationMode() == NavigationMode.Back &&
parameters.TryGetValue("navigateTo", out string navigateTo))
{
await _navigationService.NavigateAsync(navigateTo);
return;
}
}
}
Given: "NavigationPage/ViewA/ViewB/ViewC/ViewD"
Navigate from ViewD with:
NavigationService.NavigateAsync("../../../ViewE");
Results in: "NavigationPage/ViewA/ViewE"
Referred from here
Need Prism >= 7.0
Another approach would be to have your page implement INavigationAware and in the OnNavigatedFrom, call Navigatin.RemovePage(this).
I do it that way, it's simpler.
navigationService.NavigateAsync("../PageB");
I am using Prims 7.0.0.396.
I have a problem with this sample from Mvvmcross5 documentation :
public class MyViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public MyViewModel(IMvxNavigationService navigation)
{
_navigationService = navigationService;
}
public async Task SomeMethod()
{
var result = await _navigationService.Navigate<NextViewModel, MyObject, MyReturnObject>(new MyObject());
//Do something with the result MyReturnObject that you get back
}
}
public class NextViewModel : MvxViewModel<MyObject, MyReturnObject>
{
public async Task Initialize(MyObject parameter)
{
//Do something with parameter
}
public async Task SomeMethod()
{
await Close(new MyReturnObject());
}
}
It seems to work with a simple Xamarin app with Mvvmcross project. Now, i need to use it with Xamarin.Forms : the close method is called but after showing the previous page, nothing happen :/
In output : "Requesting presentation change" maybe a clue ?
I’m creating my first Firebase App. One of its requirements is that it run when the network is not available. The Firebase guide states:
Enabling disk persistence allows our app to also keep all of its state even after an app restart. We can enable disk persistence with just one line of code.
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
With disk persistence enabled, our synced data and writes will be persisted to disk across app restarts and our app should work seamlessly in offline situations.
Another requirement is to use Google Sign In. So in my MainActivity I check if the User is signed in, if not, I launch the SignInActivity. (The SignInActivity is from the Firebase examples.) The SignInActivity works, the user gets logged in, and MainActivity is launched for a second time. Now my app crashes on the code line FirebaseDatabase.getInstance().setPersistenceEnabled(true); with the following message:
Calls to setPersistenceEnabled() must be made before any other usage of FirebaseDatabase instance.
Now if I restart my app, the User is signed in, the SignInActivity is not launched, my app runs fine.
Any suggestions of how I avoid this crash after the User signs in?
As I was posting this question, I received a suggestion to relocate FirebaseDatabase.getInstance().setPersistenceEnabled(true);
to my “Application class”. I get exactly the same result … SignInActivity starts, completes, and I get a crash on the setPersistenceEnabled.
Below is my MainActivity onCreate:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Calls to setPersistenceEnabled() must be made before any other usage of FirebaseDatabase instance.
// Crash here upon returning from SignInActivity.
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
mFirebaseDbReference = FirebaseDatabase.getInstance().getReference();
// Initialize Firebase Auth
mFirebaseAuth = FirebaseAuth.getInstance();
mFirebaseUser = mFirebaseAuth.getCurrentUser();
if (mFirebaseUser == null) {
// Not signed in, launch the Sign In activity
Timber.tag("MainActivity").i("onCreate(): User not signed in, launching SignInActivity");
startActivity(new Intent(this, SignInActivity.class));
finish();
} else {
mUsername = mFirebaseUser.getDisplayName();
Timber.tag("MainActivity").i("onCreate(): User \"%s\" signed in.", mUsername);
if (mFirebaseUser.getPhotoUrl() != null) {
mPhotoUrl = mFirebaseUser.getPhotoUrl().toString();
}
}
A FirebaseApp is initialized by a ContentProvider so it is not initialized at the time onCreate() is called.
Get your FirebaseDatabase like this:
public class Utils {
private static FirebaseDatabase mDatabase;
public static FirebaseDatabase getDatabase() {
if (mDatabase == null) {
mDatabase = FirebaseDatabase.getInstance();
mDatabase.setPersistenceEnabled(true);
}
return mDatabase;
}
}
Then call Utils.getDatabase() from any activity you want.
Read more in this article
I fixed this exception by using setPersistenceEnabled(true) in my Application class.
public class MApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
}
}
In AndroidManifest.xml, set the application name as MApplication:
<application
android:name=".MApplication"
... />
I was facing a similar problem and using a static variable seemed to resolve the issue for me. So at first my code looked something like this
#Override
protected void onCreate(Bundle savedInstanceState) {
//..code
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
FirebaseDatabase database = FirebaseDatabase.getInstance();
//..code
}
and now it looks more like
static boolean calledAlready = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
//..code
if (!calledAlready)
{
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
calledAlready = true;
}
FirebaseDatabase database = FirebaseDatabase.getInstance();
//..code
}
Hope it helps!
I bit late but today i got this issue, I solved by adding
static {
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
}
to my Activity
To me, it is easier to be handled by creating a separate class for Firebase. This is because Firebase has its own instance and if you are using it in more than one activity, there's the possibility for it to crash if you call setPersistenceEnabled again in another activity.
Another good thing is that you can pass your context or parameters into the FirebaseHandler constructor if required. Or if you have fixed location in the database, they can be called easy without the .child("location") boilerplate.
Example:
public class FirebaseHandler {
// parameters
private Context context;
private String userKey;
private DatabaseReference databaseReference;
private static boolean isPersistenceEnabled = false;
private static String fixedLocationA = "locationA";
private static String fixedLocationB = "locationB";
public FirebaseHandler(Context context, String userKey) {
this.context = context; // context can be used to call PreferenceManager etc.
this.userKey = userKey;
if (!isPersistenceEnabled) {
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
isPersistenceEnabled = true;
}
databaseReference = FirebaseDatabase.getInstance().getReference().child(userKey);
}
public DatabaseReference getRefA() {
return databaseReference.child(fixedLocationA);
}
public DatabaseReference getRefB() {
return databaseReference.child(fixedLocationB);
}
}
This can then be called in any Activity as below.
public class MyActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// get instance
FirebaseHandler firebaseHandler = new FirebaseHander(this, "userKey");
// to set value
firebaseHandler.getRefA().setValue("value");
// to set listener
firebaseHandler.getRefB().addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// TODO here....
// also, can remove listener if required
if (certain condition) {
firebaseHandler.getRefB().removeEventListener(this);
}
}
}
}
}
I'm facing some problem too, but this is my temporary solution for my app.
Create BaseActivity extends AppcompatActivity and override onCreate, put setPersistenceEnabled there.
public class BaseActivity extends AppCompatActivity {
private static String TAG = "BaseActivity";
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try{
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
Log.d(TAG,FirebaseDatabase.getInstance().toString());
}catch (Exception e){
Log.w(TAG,"SetPresistenceEnabled:Fail"+FirebaseDatabase.getInstance().toString());
e.printStackTrace();
}
}
}
And change MainActivity to extend BaseActivity
public class MainActivity extends BaseActivity
EDIT: Follow #imakeApps answer
public class BaseActivity extends AppCompatActivity {
private static String TAG = "BaseActivity";
static boolean isInitialized = false;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try{
if(!isInitialized){
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
isInitialized = true;
}else {
Log.d(TAG,"Already Initialized");
}
}catch (Exception e){
e.printStackTrace();
}
}
}
Solved it by making the Firebase reference a static class field like this:
public class MainActivity extends AppCompatActivity
private static FirebaseDatabase fbDatabase;
#Override
protected void onCreate(Bundle savedInstanceState) {
if(fbDatabase == null) {
fbDatabase = FirebaseDatabase.getInstance();
fbDatabase.setPersistenceEnabled(true);
}
It's no problem to create new Firebase references (without setPersistenceEnabled(true)) in other activities too.
If you don't like the static fields, this did the trick for me:
if (FirebaseApp.getApps(context).isEmpty()) {
FirebaseApp.initializeApp(context);
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
}
This can be caused be more that one process initializing twice firebase or Multidex apps. For more info see this: https://github.com/firebase/quickstart-android/issues/15
I wouldn't recommend using Application to store the data because like its written in CodePath
There is always data and information that is needed in many places within your app. This might be a session token, the result of an expensive computation, etc. It might be tempting to use the application instance in order to avoid the overhead of passing objects between activities or keeping those in persistent storage.
However, you should never store mutable instance data inside the Application object because if you assume that your data will stay there, your application will inevitably crash at some point with a NullPointerException. The application object is not guaranteed to stay in memory forever, it will get killed. Contrary to popular belief, the app won’t be restarted from scratch. Android will create a new Application object and start the activity where the user was before to give the illusion that the application was never killed in the first place.
Thats the reason I would recommend using a Singleton like this:
public class DataBaseUtil {
private static FirebaseDatabase mDatabase;
public static FirebaseDatabase getDatabase() {
if (mDatabase == null) {
mDatabase = FirebaseDatabase.getInstance();
mDatabase.setPersistenceEnabled(true);
}
return mDatabase;
}}
just use it in your code then like
private FirebaseDatabase fdb = DataBaseUtil.getDatabase();
Create a class called Util.java
and add following code
public class Util {
private static FirebaseDatabase mData;
public static FirebaseDatabase getDatabase() {
if (mData == null) {
mData = FirebaseDatabase.getInstance();
mData.setPersistenceEnabled(true);
}
return mData;
}
}
Now replace FirebaseDatabase.getIntance() with Util.getDatabase() every time in each activity. Calling just once will get the error!
I was facing same issue. i changed code as below.
BEFORE(Causing Crash)
var rootRef = FIRDatabase.database().reference()
override func viewDidLoad() {
super.viewDidLoad()
FIRDatabase.database().persistenceEnabled = true
}
AFTER (Resolved Crash)
var rootRef:FIRDatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
FIRDatabase.database().persistenceEnabled = true
rootRef = FIRDatabase.database().reference()
}
In Menifest
android:name=".AppName
Create java Class that extends Application
public class AppName extends Application {
#Override
public void onCreate() {
super.onCreate();
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
}
Make sure that .setpersistenceenabled(true) is not happening twice, while sign in by Google in your case, second care setPersistenceEnabled(true) is must be called before any instance of firebase called this resolve my issue.
For Kotlin Try this:
class DatabaseUtil {
companion object {
private val firebaseDatabase: FirebaseDatabase = FirebaseDatabase.getInstance()
init {
firebaseDatabase.setPersistenceEnabled(true)
}
fun getDatabase() : FirebaseDatabase {
return firebaseDatabase
}
}
}
Simply move you code in ExampleFragment.class from onCreateView method to onCreate method:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
....
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_home, container, false);
The error message describes the problem:
Calls to setPersistenceEnabled() must be made before any other usage
of FirebaseDatabase instance.
The fix to this problem is described into the documentation: As in SDK 2.x, persistence of the disk must be enabled before other calls to the database are made.
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
https://firebase.google.com/support/guides/firebase-android
You can use:
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
before using FirebaseDatabase.getInstance().getReference();