How to use a single Firestore instance throughout an android app? - java

I'm trying to use a single Firestore instance throughout an app because with Firebase's cold start the first query or the first data to be fetched takes sometime(like 2000ms-3000ms). I tried having Firestore object as static in a class that extends Application but it shows a Lint warning "Do not place Android context classes in static fields; this is a memory leak". I don't know where to go from here. Please help me.

getInstance() will get you an unique instance throught your whole app, when you use
FirebaseFirestore.getInstance()
or
FirebaseDatabase.getInstance()
It will not create another instance if you are creating them in different classes, instead, it will reuse the same one over and over again.
You can also see the implementation by ctrl + click on the method in your IDE
#NonNull
public static FirebaseFirestore getInstance() {
FirebaseApp app = FirebaseApp.getInstance();
if (app == null) {
throw new IllegalStateException("You must call FirebaseApp.initializeApp first.");
} else {
return getInstance(app, "(default)");
}
}
#NonNull
public static FirebaseApp getInstance() {
synchronized(LOCK) {
FirebaseApp defaultApp = (FirebaseApp)INSTANCES.get("[DEFAULT]");
if (defaultApp == null) {
throw new IllegalStateException("Default FirebaseApp is not initialized in this process " + ProcessUtils.getMyProcessName() + ". Make sure to call FirebaseApp.initializeApp(Context) first.");
} else {
return defaultApp;
}
}
}

Related

How to Access Activity from ActivityScenarioRule

I am using ActivityScenarioRule for Espresso UI Testing and I wanted to get access to the method getStringArray(), calling which requires the Activity . So, is there any way to retrieve the Activity by the ActivityScenarioRule , maybe something similar to getActivity in ActivityTestRule.
#Rule
public ActivityScenarioRule activityScenarioRule = new ActivityScenarioRule<>(MainActivity.class);
I am not using ActivityTestRule, because it is deprecated!
Since it appears you're using Java, here's how you'd do it:
#Rule
ActivityScenarioRule<MainActivity> activityScenarioRule = new ActivityScenarioRule<>(MainActivity.class);
#Test
public void test() {
activityScenarioRule.getScenario().onActivity(activity -> {
// use 'activity'.
});
}
Please read the documentation for more info on these new ways of interacting with the activity under test.
For anyone who wants Activity, but that without need to re-write all tests to run on UI-thread, a fairly straightforward Java way to get it:
Waiting for UI
Assume you want to test if a dialog is shown after some delay, the onActivity(...) hook runs on UI-thread, which means waiting in there would cause the dialog to be nerver shown.
In such cases you need to keep a strong-reference to ActivityScenario (as that prevents Activity close).
Test should wait for onActivity(...) hook to be called, then keep passed Activity's reference.
Finally, move test logic out of onActivity(...) hook.
Example
private ActivityScenario mActivityScenario;
#After
public void tearDown() throws Exception {
if (mActivityScenario != null) {
mActivityScenario.close();
}
mActivityScenario = null;
}
#Override
public Activity getActivity() {
if (mActivityScenario == null) {
mActivityScenario = ActivityScenario.launch(getActivityClassForScenario());
}
return tryAcquireScenarioActivity(mActivityScenario);
}
protected static Activity tryAcquireScenarioActivity(ActivityScenario activityScenario) {
Semaphore activityResource = new Semaphore(0);
Activity[] scenarioActivity = new Activity[1];
activityScenario.onActivity(activity -> {
scenarioActivity[0] = activity;
activityResource.release();
});
try {
activityResource.tryAcquire(15000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Assert.fail("Failed to acquire activity scenario semaphore");
}
Assert.assertNotNull("Scenario Activity should be non-null", scenarioActivity[0]);
return scenarioActivity[0];
}
Espresso states the following:
At the same time, the framework prevents direct access to activities
and views of the application because holding on to these objects and
operating on them off the UI thread is a major source of test
flakiness.
When there is no other way I use the following method to get an arbitrary activity from an ActivityScenarioRule. It uses onActivity mentioned in the accepted answer:
private <T extends Activity> T getActivity(ActivityScenarioRule<T> activityScenarioRule) {
AtomicReference<T> activityRef = new AtomicReference<>();
activityScenarioRule.getScenario().onActivity(activityRef::set);
return activityRef.get();
}
Any onView(...) code inside onActivity led to a timeout in my testcases. So, I extracted the activity and used it with success outside the onActivity. Beware tho! See the statement above.
#Test
fun checkForUpdate() {
val scenario = ActivityScenario.launch(MainActivity::class.java)
scenario.onActivity {
UpdateTool.checkForUpdate(it)
}
}

Getting LiveData inside the Repository class and update the Data inside the Database without an endless loop

I'm trying to create an app that only adds an entry to the database if there is no entry already at a specific time intervals and modifies the existing entry if there is already one in the database. I'm using Room.
It works, but only with a workaroud, because I have to call the add function twice before the value gets added (make the input two times before it works). And I also don't like my adding the Observer and immediately removing it afterwards. I also had to implement the workaround when instatiating the DB, with a value when it was first created.
How can I get the data from my LiveData List inside the Repository class and change it without ending up in an endless loop or how do I have to redesign my code to avoid that?
The complete code can be found on my Github account: Github repository
I would really appreciate any suggestion fix my problem and learn to design and plan my code better.
MainActivity
public void ok_clicked(View view) {
Intent intent = new Intent(this, DataActivity.class);
...
Diary addDiary = new Diary(new Date(), diaryCh.isChecked(), readingCh.isChecked(),writingCh.isChecked(),pianoCh.isChecked(),youtubeCh.isChecked());
mDiaryViewModel.insert(addDiary);
startActivity(intent);
}
DiaryViewModel
public void insert(Diary diary) {mRepositroy.add(diary);}
DiaryRepository
public class DiaryRepository {
private DiaryDao mDiaryDao;
private LiveData<List<Diary>> mEntriesToday;
DiaryRepository(Application application) {
AppDatabase db = AppDatabase.getDatabase(application);
mDiaryDao = db.diaryDao();
mEntriesToday = mDiaryDao.findEntriesByDate(Dates.getYesterdayMidnight(), Dates.getTomdayMidnight());
}
LiveData<List<Diary>> getmEntriesToday() { return mEntriesToday;}
void add(Diary diary) {
Observer<List<Diary>> observerEntriesToday = new Observer<List<Diary>>() {
#Override
public void onChanged(List<Diary> diaries) {
if (diaries != null) {
Log.e(TAG, "add: with matching entries"+ diaries.get(0) + " add: " + diary );
diaries.get(0).addAttributes(diary);
new updateDiaryAsyncTask(mDiaryDao).execute(diaries.get(0));
} else {
Log.e(TAG, "add: without matching entries"+" add: " + diary );
new insertDiaryAsyncTask(mDiaryDao).execute(diary);
}
}
};
getmEntriesToday().observeForever(observerEntriesToday);
getmEntriesToday().removeObserver(observerEntriesToday);
}
You shouldn't be using LiveData in this scenario at all. It is only a wrapper for data that will be observed from Activity/Fragment.
First you need to modify mEntriesToday to be MutableLiveData so you can update it.
In your case, you can omit using Observer for updating DB, and so something simple like:
void add(Diary diary){
if (mEntriesToday.getValue() != null) {
Log.e(TAG, "add: with matching entries"+ mEntriesToday.getValue().get(0) + " add: " + diary );
mEntriesToday.getValue().get(0).addAttributes(diary);
new updateDiaryAsyncTask(mDiaryDao).execute(mEntriesToday.getValue().get(0));
} else {
Log.e(TAG, "add: without matching entries"+" add: " + diary );
new insertDiaryAsyncTask(mDiaryDao).execute(diary);
}
}
If you need this data, outside this class, then you can use getmEntriesToday() and observe it.
You can get the value of the LiveData using the getValue() method
void add(Diary diary) {
List<Diary> diaries = mEntriesToday.getValue();
if(diaries!=null){
diaries.get(0).addAttributes(diary);
//update
}else{
//insert
}

Strange LiveData behaviour?

Im trying to implement MVVM architecture using ViewModel and LiveData. These two methods are inside a Activity:
private void handleResult(BoardViewModel vm) {
vm.getLiveDataSingleObj("Result").observe(this, new Observer<Object>() {
#Override
public void onChanged(#Nullable Object resultObj) {
Result result = (Result) resultObj;
if (!result.isCompleted()) return;
gotoResult();
}
});
}
And
private void gotoResult() {
Log.w(LOG_TAG, "Result: Moving to next activity");
Intent intent = new Intent(boardActivity, ResultActivity.class);
intent.putExtra("LEVEL", levelIndex);
intent.putExtra("MAP", mapIndex);
startActivity(intent);
}
The handleResult method is setup to listen for result objects that indicate that the game has ended and it is time to move on to the next activity ("gotoResult"). However, this completely breaks the navigation of the app, when i go back and then say attempt to start a new game session i instead instantly go to the next activity telling me I've already won.
Any ideas as to why it fires multiple times and eventually stops, letting me start a new session. To clarify, if I remove the gotoResult the logic works every single time no errors with indexes out of bounds or what have you, it's only when I add the goto that everything breaks.
ViewModel:
private void setupHashTypes() {
hashLiveData.put(KEY_BOARD, liveDataBoardQuery);
hashLiveData.put(KEY_STEPS_COUNTER, game.getStepsTakenLiveData());
hashLiveData.put(KEY_PATH_CHANGE, game.getPathChangedLiveData());
hashLiveData.put(KEY_VALUE_CHANGE, game.getValueChangeLiveData());
hashLiveData.put(KEY_TIMER, game.getTimerLiveData());
hashLiveData.put(KEY_SELECTED, game.getSelectedLiveData());
hashLiveData.put(KEY_DESELECTED, game.getDeselectedLiveData());
hashLiveData.put(KEY_HOLD, game.getHoldLiveData());
hashLiveData.put(KEY_UNHOLD, game.getUnholdLiveData());
hashLiveData.put(KEY_RESULT, game.getResultLiveData());
}
public LiveData<Object> getLiveDataSingleObj(String type) {
if (hashLiveData.containsKey(type)) {
return (LiveData<Object>) hashLiveData.get(type);
}
throw new IllegalArgumentException("Invalid: key was not found: " + type);
}
And the Model has getters, example:
private final SingleLiveEvent<Result> resultLiveData = new SingleLiveEvent<>();
public LiveData<Result> getResultLiveData() {
return resultLiveData;
}
you should remove the observer in onDestroy() method
Changing from MutableLiveData which always resends the previous set values to new subscribers, to SingleLiveEvent which doesn't have this behaviour, solved the problem.
The class can be found here: https://github.com/googlesamples/android-architecture/tree/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp

Android Realm: No results when added in singleton

I'm testing out Realm for database storage.
I'm using a singleton for fetching and managing common data that needs to be refreshed fairly often. But it seems that the Realm defaultInstance that get in my singleton is not in the same scope as if I get it in my Activity. So when I fetch remote data via my singleton, then save to realm, I can't retrieve that data from an Activity (get an empty result set).
I have attempted to pass in the Realm instance I defined in the Activity to the singleton (and close it there as well), but I still cannot retrieve saved results via the Activity instance.
I'm setting the default configuration in my Application class if that makes a difference.
Any help would be appreciated in clearing this up.
**Edit
Here's some more code. I'm using retrofit and gson, and my relevant services are in a Utility class (which may be causing the issue).
private void fetchMyObjects(Context context) {
// Fetch the myObjects
UtilityServices utilityServices = new UtilityServices(context);
utilityServices.getMyObjects(new UtilityServices.MyObjectsListener() {
#Override
public void gotMyObjects(final ArrayList<MyObject> myObjects, Exception e) {
if(e == null) {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.delete(MyObject.class);
realm.copyToRealm(myObjects);
Log.v("qwer", "LocalDataFragment fetchMyObjects: " + realm.where(MyObject.class).findAll().size());
}
});
} finally {
if(realm != null) {
realm.close();
}
}
} else {
// TODO: Handle a myObject error.
e.printStackTrace();
}
}
});
}
There is only one way that the results of a Realm transaction would not be visible after a transaction is executed and that is that the transaction takes place on a different thread.
It seems quite likely that this is the case, in your code, since, if getMyObjects ran on the UI thread, you would be getting the "no network activity on the UI thread exception"

Android VPN Service Builder.establish() returns null

I am trying to make a VPN Service work on android.
To do this, I think I have to use the Builder class provided in the VpnService class, which is provided by the android SDK. Now when I try to do this, the method
builder.establish()
returns null. Here is the full method I wrote;
// If theInterface still exists from last time, reuse it, it has the same parameters.
if (theInterface != null) {
return;
}
// configure the VPN settings.
Builder builder = new Builder();
builder.setMtu(1500);
builder.addAddress("10.0.2.0", 32);
builder.addRoute("0.0.0.0", 0);
try {
theInterface.close();
} catch (Exception e) {
// Ignore
}
theInterface = builder.establish();
Log.i(TAG + "IMPORTANT", builder.toString());
The code crashes at:
Log.i(TAG + "IMPORTANT", builder.toString());
When debugging, the debugger says 'theInterface = null' throughout the entire execution of the program.
EDIT:
I have looked trough the Builder class which is located in android.net.VpnService
In this class, it seems that there is a hidden class (IConnectivityManager), maybe my problem has something to do with this...
Here is the piece of code I'm talking about.
/**
* Use IConnectivityManager since those methods are hidden and not
* available in ConnectivityManager.
*/
private static IConnectivityManager getService() {
return IConnectivityManager.Stub.asInterface(
ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
}
as VpnService.Builder.establish documents:
Returns ParcelFileDescriptor of the VPN interface, or null if the
application is not prepared.
need call VpnService.prepare: http://developer.android.com/reference/android/net/VpnService.html#prepare(android.content.Context) first

Categories

Resources