I have the following Reporting code:
public class Reporting {
private ExtentHtmlReporter extentHtmlReporter;
private static ThreadLocal<ExtentReports> extentReports = new ThreadLocal<>();
private static ThreadLocal<ExtentTest> extentTest = new ThreadLocal<>();
public synchronized ExtentTest createInstanceReport(String testCaseName) {
System.out.println(extentReports.get());
new File(Constants.userDir + "/Reports/").mkdirs();
// To generate report with name
extentHtmlReporter = new ExtentHtmlReporter(
Constants.userDir + "/Reports/" +
"ExecutionReport_" + new SimpleDateFormat(
Constants.date).format(new Date()) + ".html");
// Setting Document Title
extentHtmlReporter.config().setDocumentTitle("Demo");
// Setting Report Name
extentHtmlReporter.config().setReportName("Demo Automation");
// Setting Theme
extentHtmlReporter.config().setTheme(Theme.STANDARD);
// Setting Chart location
extentHtmlReporter.config().setTestViewChartLocation(ChartLocation.TOP);
// Setting Chart visibility
extentHtmlReporter.config().setChartVisibilityOnOpen(false);
// Setting Time stamp
extentHtmlReporter.config().setTimeStampFormat("yyyy-MM-dd HH:mm:ss");
// Setting append exist as true
extentHtmlReporter.setAppendExisting(true);
ExtentReports extentReports = new ExtentReports();
extentReports.attachReporter(extentHtmlReporter);
// Setting system info
extentReports.setSystemInfo("Name",
BaseTest.prop.getProperty(Constants.testerName));
extentReports.setSystemInfo("Environment",
BaseTest.prop.getProperty(Constants.environment));
extentReports.setSystemInfo("Browser",
BaseTest.prop.getProperty(Constants.browser));
Reporting.extentReports.set(extentReports); // Instead of using here extentReport thread like this, Can anyone suggest to use it directly
// Add test case name in report
ExtentTest extentTest = Reporting.extentTest.get();
extentTest = Reporting.extentReports.get().createTest(testCaseName);
Reporting.extentTest.set(extentTest);
// Assigning categories
extentTest.assignCategory(MultiFunction.getProp()
.getProperty(Constants.browser));
System.out.println(Reporting.extentReports.get());
System.out.println(Reporting.extentTest.get());
return extentTest;
}
public synchronized ExtentTest getExtentTest() {
return extentTest.get();
}
public synchronized ExtentReports getInstanceReport() {
return extentReports.get();
}
public synchronized void remove() {
extentReports.remove();
extentTest.remove();
}
}
I was trying parallel testing using TestNG (and will have to use Selenium grid and sauce in future). I execute 2 test cases then only one test case result is added in the report.
I have isolated the extentTest, extentReporter and WebDriver instances using threadPool.
Tried below with extentHtmlReporter instance:
1) Tried to make it static(no luck)
2) Tried to make it local (the same behaviour, getting only 1 test case result)
3) Tried as a non-static global variable ( no luck)
Could you suggest how to solve the above issue?
Please note: Only one report is generated. But when I tried to run parallel test cases in debug mode reports are generated for both the test case. I think because one test case gets over its killing some instance (when running in non-debug mode)
Also, I want to redesign the following place in my code:
For extentRpeort, I am using:
Reporting.extentReports.set(extentReports);
To add extentReport instance to my extentReport Thread.
Instead of adding like this I want to use it directly so as to reduce line of code.
If I understand correctly you have to generate Report from all executed TestNG cases.
However, from code which you shared, it is very visible that you will have some trouble with it. You are making a few critical mistakes and result are obvious:
For generating reports with TestNG I will suggest grabbing information about test execution from TestNG listener. Something like:
public final class TestNGListener extends TestListenerAdapter implements IInvokedMethodListener, ISuiteListener {
#Override
public void onStart(ITestContext context) {
Logger.info(buildMessage(Logger.PREFIX_TEST_STARTED, context.getName()));
}
#Override
public void onFinish(ITestContext context) {
Logger.info(buildMessage(Logger.PREFIX_TEST_FINISHED, context.getName()));
}
#Override
public void onTestStart(ITestResult result) {
Logger.info(buildMessage(Logger.PREFIX_METHOD_STARTED, getMethodName(result)));
}
#Override
public void onTestSuccess(ITestResult result) {
Logger.info(buildMessage(Logger.PREFIX_METHOD_SUCCESS, getMethodName(result)));
processTestResult(result);
}
#Override
public void onTestFailure(ITestResult result) {
Logger.info(buildMessage(Logger.PREFIX_METHOD_FAILED, getMethodName(result)));
}
You can't do everything in one method! You broke Single Responsibility Principle. Your createInstanceReport() is doing all jobs (setting report details, set system info, attach an executed test case to report) at one place. You have to redesign this logic to some logical separate operations. After redesigning your problem with the next line:
Reporting.extentReports.set(extentReports)
Could successfully disappear.
You have to consider a case, why you need to use exactly Extent, Reports Version 3. TestNG has test reports from the box. They are poor but they are presented out of the box. If you want just to improve it a little bit you could use ReportNG over TestNG.
It is quite easy to configure: Configuring ReportNG with TestNG for HTML Reports.
It isn't maintained, but it makes TestNG reports really eye candy and understandable.
Anyway, my suggestion is to use TestNGListener for getting info about test cases execution. And read more about good programming practice.
Work with TestNG/jUnit (or other runner framework that you are using) listener, here is a good example how to do it.
Do not put everything in a single class.
https://www.swtestacademy.com/extent-reports-version-3-reporting-testng/
The issue was with the flushing of extent report instance.
I was using ThreadLocal for storing extent report instance and was flushing the wrong instance.
Related
Firstly, my setup is RXJava 1, Retrofit 2 and I'm using Java 7.
I have a method that, when it is called, will set an atomic boolean to true.
That method then calls a retrofit API.
Upon completion, timeout etc... the atomic boolean is reset to false.
So, I would therefore like to uni test that when I call my method, the Atomic Boolean is set to true.
So, I do the following:
assertFalse(orderUseCase.isOrderInProcess());
orderUseCase.execute(id, orderWrapper, ts);
assertTrue(orderUseCase.isOrderInProcess());
Test that the boolean is false.
Execute my use case
Test that the boolean is true.
Now, in order to perform the last test I need to ensure the API does nothing when it is called (the execute method will call the retrofit API.
To try and doNothing I am using the following line at the start of my test case.
doNothing().when(orderAPI.orderComplete(anyString(), any(OrderWrapper.class)));
I am however receiving the following error:
org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at com.tfds.xms.unit_test.SingleTest.TestAtomicBooleanLocked(SingleTest.java:90)
E.g. thenReturn() may be missing.
Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
Hints:
1. missing thenReturn()
2. you are trying to stub a final method, you naughty developer!
3: you are stubbing the behaviour of another mock inside before 'thenReturn' instruction if completed
From reading where other people had a similar issue, the answers were suggesting that their 'when' line was calling another Mock (which I don't believe I am doing) or that they hadn't done the 'PrepareForTest' step which I have done. I am however wondering if what is in my prepareForTest section is correct or not...
Incidentally I understand that the error is pointing me to add a 'thenReturn' but that would mean that the API 'does' something and would consequently cause the Atomic Boolean to unlock again, which is not what I want..
Any help is very much appreciated.
My full test case is:
#RunWith(TestRunner.class)
#PrepareForTest({OrderApiService.class, OrderUseCase.class})
public class SingleTest {
#Rule
public RxJavaResetRule pluginsReset = new RxJavaResetRule();
private OrderApiService orderAPI;
private OrderUseCase orderUseCase;
private OrderRepository orderRepository;
#Mock App app;
#Before
public void setUp() {
orderAPI = mock(OrderApiService.class);
orderRepository = new OrderRepository(app, orderAPI);
orderUseCase = new OrderUseCase(orderRepository);
}
#Test
public void TestAtomicBooleanLocked() throws Exception {
doNothing().when(orderAPI.orderComplete(anyString(), any(OrderWrapper.class)));
String id = "5";
Order order = new Order();
OrderWrapper orderWrapper = new orderWrapper(order);
TestSubscriber<GenericResponse> ts = new TestSubscriber<GenericResponse>();
assertFalse(orderUseCase.isOrderInProcess());
orderUseCase.execute(id, orderWrapper, ts);
assertTrue(orderUseCase.isOrderInProcess());
}
}
The doNothing() method from Mockito works a bit different then the standalone when() method. You should be fine by just pushing the method call out of the .doNothing() like this:
doNothing().when(orderAPI)
.orderComplete(anyString(), any(OrderWrapper.class));
Happy testing!
The workflow should be the following:
Activity starts
Progress bar is visible
Network request fires (idling resource is already registered so espresso knows how to wait for it).
Progress bar is hidden
Text from network is shown.
Up to this point, I have written assertions for steps 1, 3, 5 and it works perfectly:
onView(withText("foo 1"))
.check(matches(isDisplayed()));
Problem is, I have no idea how to let espresso know to verify the visibility of progress bar before the request is made and after the request is made.
Consider the onCreate() method is the following:
super.onCreate(...);
setContentView(...);
showProgressBar(true);
apiClient.getStuff(new Callback() {
public void onSuccess() {
showProgressBar(false);
}
});
I have tried the following but it doesn't work:
// Activity is launched at this point.
activityRule.launchActivity(new Intent());
// Up to this point, the request has been fired and response was
// returned, so the progress bar is now GONE.
onView(withId(R.id.progress_bar))
.check(matches(isDisplayed()));
onView(withId(R.id.progress_bar))
.check(matches(not(isDisplayed())));
The reason this is happening is because, since the client is registered as an idling resource, espresso will wait until it is idle again before running the first onView(...progressbar...)... so I need a way to let espresso know to run that BEFORE going to idle.
EDIT: this doesn't work either:
idlingResource.registerIdleTransitionCallback(new IdlingResource.ResourceCallback() {
#Override
public void onTransitionToIdle() {
onView(withId(R.id.progress_bar))
.check(matches(isDisplayed()));
}
});
Espresso has problems with the animation. You can just set the drawable of the progress bar to something static just for the test and it works as expected.
Drawable notAnimatedDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.whatever);
((ProgressBar) getActivity().findViewById(R.id.progress_bar)).setIndeterminateDrawable(notAnimatedDrawable);
onView(withId(R.id.progress_bar)).check(matches(isDisplayed()));
As I can see Espresso is tightly coupled with skipping dynamic UI actions whatsoever, that's why you can't test ProgressBar using Espresso. However, you can easily accomplish this with another Android Google tool: UiAutomator as following:
saveButton().click(); // perform action opening ProgressBar with UiAutomator, not Espresso
assertTrue(progressBar().exists());
Using these static utils:
public static UiObject progressBar() {
return uiObjectWithText(R.string.my_progress);
}
public static UiObject saveButton() {
return uiObjectWithId(R.id.my_save_button);
}
public static UiObject uiObjectWithId(#IdRes int id) {
String resourceId = getTargetContext().getResources().getResourceName(id);
UiSelector selector = new UiSelector().resourceId(resourceId);
return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}
public static UiObject uiObjectWithText(#StringRes int stringRes) {
UiSelector selector = new UiSelector().text(getTargetContext().getString(stringRes));
return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}
Make sure your build.gradle includes:
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
It looks like this may not be truly possible. Though it is an older group posting, there is a fairly decisive answer in the Android Test Kit Discussion where it is stated that the UI threads don't rest during the animation of progress bars, and so the Espresso framework cannot execute.
Marcus Klepp recommends moving past this here through the use of build types. The Gradle plugin will permit you to define different build types. You could set up a different layout in your androidTest build type which replaces the View in question with something generic. If all you're doing is confirming that the widget isDisplayed() under one set of conditions, and not(isDisplayed()) under another set of conditions, then you could surely implement that through different layout files. Not that it is not a little bit of a lift.
Finally, there may be another post here which carries some additional information here: "java.lang.RuntimeException: Could not launch intent" for UI with indeterminate ProgressBar
In my case solution which was provided above works as well but I simplify it, so added build.gradle uiautomator library
androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
and created a new class which will work for example with Progress bar
public class ProgressBarHandler {
public static void waitUntilGoneProgressBar() {
progressBar().waitUntilGone(10000);
}
private static UiObject progressBar() {
return uiObjectWithId(R.id.progress_bar);
}
private static UiObject uiObjectWithId(#IdRes int id) {
String resourceId = getTargetContext().getResources().getResourceName(id);
UiSelector selector = new UiSelector().resourceId(resourceId);
return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}
}
and in my tests use all Espresso methods and when needed only then address to UiAutomator in tests, for example
public class LoginTest extends AbstractTest {
#Rule
public ActivityTestRule<LoginActivity> createAccountActivityTestRule = new ActivityTestRule<>(LoginActivity.class);
#Test
public void loginTest() {
onView(withId(R.id.login_email)).perform(typeText("autotest666#gmail.com"));
onView(withId(R.id.input_password)).perform(typeText("Password123."));
onView(withId(R.id.login_log_in)).perform(click());
waitUntilGoneProgressBar();
onView(withId(R.id.fragment_home_title)).check(matches(isDisplayed()));
}
I'm writing UI tests with Espresso. App cooperates tightly with server, so in many cases, I need to wait for either value to be calculated, or data is got and displayed, etc. Espresso suggests using IdlingResource for this.
My IdlingResource classes look like this (simple and clear example).
public class IRViewVisible implements IdlingResource {
private View view;
private ResourceCallback callback;
public IRViewVisible(View view) {
this.view = view;
}
#Override
public String getName() {
return IRViewVisible.class.getName();
}
#Override
public boolean isIdleNow() {
if(view.getVisibility() == View.VISIBLE && callback != null) {
callback.onTransitionToIdle();
return true;
}
return false;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.callback = resourceCallback;
}
}
Please correct me if I'm wrong anywhere (as sometimes it seems to me that my IdlingResources do not work properly).
I register the idling resource in setUp() like this:
IRViewVisible ir = new IRViewVisible(View v);
Espresso.registerIdlingResources(ir).
Unregister it on tearDown().
I found this article (there is a section called "Register a component tied to an Activity instance") — I do not use his schema, but I checked hashcode of view that was set to IdlingResource after registering (in each method), and it's not the same view — all hashes are different.
Another question: One Test class (it's results) can't have any effect on another Test class, can it?
I'm guessing your problem stems from getName() returning the same name for all instances of IRViewVisible. This means you can only have one registered instance of it at a time - any subsequent registrations will fail (silently!).
You mention that you clear the IdlingResources at the end of each test, but if you are register multiple instances of it at once, you need to make sure each instance has a unique name. it's not clear from your question if you're registering multiple instances of IRViewVisible in a single test.
As to your final question: Yes, it is possible. Android doesn't completely shut down the Application between test runs - just the Activities. Common things which can cause problems:
Failing to clear persistent state (saved data).
Failing to clear global state - e.g. static variables/singletons
Not waiting for background threads to finish running.
As an aside, it's worth noting that you only call onTransitionToIdle() inside isIdleNow(). This works (thanks #Be_Negative for the heads up!) but it could slow down your tests a lot, since Espresso will only poll isIdleNow() every few seconds. If you call onTransitionToIdle() as soon as the view becomes visible, it should speed things up considerably.
I needed something similar to your IRViewVisible myself, here's my effort.
So the isIdleNow() method will never return true if you don't set a callback to the idlingResource?
I reckon it's better to refactor it like this:
#Override
public boolean isIdleNow() {
boolean idle = view.getVisibility() == View.VISIBLE;
if(idle && callback != null) {
callback.onTransitionToIdle();
}
return idle;
}
Well, first of all you shouldn't need to use Espresso IdlingResource to test server calls. If you use AsyncTasks in your server calls, Espresso will be able to know when to be idle and when not. If this is not enough: try to refactor your code in this way:
IRViewVisible idlingResource = new IRViewVisible(yourView);
IdlingPolicies.setMasterPolicyTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
// Now we wait
Espresso.registerIdlingResources(idlingResource);
// Stop and verify
// Clean up
Espresso.unregisterIdlingResources(idlingResource);
Hope to be helpful.
is it safe to write such compatible code on Android?
if (Build.os.SDK_INT >= 11) {
newClass instance = new newClass();
....
}
else {
oldClass instance = new oldClass();
....
}
one of my colleagues argue with me that ClassNotFoundException might be thrown up when running the above code since ClassLoader is attempting to load newClass on an android os device which is below android 11. But I've tried couple times, and didn't see this happen.
After googling around for couple hours, I didn't find any information on how and when android default classLoader loads a specific class.
You should check the compatability like the following... It gives you more accurate than the above:
private static int currentApi = 0;
public static int getApiLevel() {
if (currentApi > 0) {
return currentApi;
}
if (android.os.Build.VERSION.SDK.equalsIgnoreCase("3")) {
currentApi = 3;
} else {
try {
Field f = android.os.Build.VERSION.class.getDeclaredField("SDK_INT");
currentApi = (Integer) f.get(null);
} catch (Exception e) {
return 0;
}
}
return currentApi;
}
you can alway use reflection to check if the class exists:
try {
Class.forName("yourclass")
} catch (ClassNotFoundExecption) {
oldClass instance = new oldClass();
}
Yes, this is safe to do on recent versions of Android. I want to say froyo and above, but it may have been even earlier than that. I don't recall for sure.
What happens is that dalvik performs a verification pass on the dex file at install time. For any classes/methods/fields that it can't resolve, it replaces those accesses with an instruction that throws a VerifyError.
In your example, if that code got loaded on, e.g. api 10, newClass instance = new newClass() would conceptually be replaced with throw new VerifYError(). So as long as that branch never gets executed at runtime, everything is good.
Short answer - don't do it.
Most VMs only load a class when it is absolutely needed. However a class loader is allowed to cache binary representation of classes beforehand[1].
Class loaders are allowed to cache binary representations of types,
load types early in anticipation of eventual use, or load types
together in related groups.
[1] - http://www.artima.com/insidejvm/ed2/lifetype2.html
[2] - http://developer.android.com/tools/extras/support-library.html
Edit - Have you checked if the class you need is available as part of the android support package ? [2]
With the release of Gingerbread, I have been experimenting with some of the new API's, one of them being StrictMode.
I noticed that one of the warnings is for getSharedPreferences().
This is the warning:
StrictMode policy violation; ~duration=1949 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=23 violation=2
and it's being given for a getSharedPreferences() call being made on the UI thread.
Should SharedPreferences access and changes really be made off the UI thread?
I'm glad you're already playing with it!
Some things to note: (in lazy bullet form)
if this is the worst of your problems, your app's probably in a good spot. :) Writes are generally slower than reads, though, so be sure you're using SharedPreferenced$Editor.apply() instead of commit(). apply() is new in GB and async (but always safe, careful of lifecycle transitions). You can use reflection to conditionally call apply() on GB+ and commit() on Froyo or below. I'll be doing a blogpost with sample code of how to do this.
Regarding loading, though...
once loaded, SharedPreferences are singletons and cached process-wide. so you want to get it loaded as early as possible so you have it in memory before you need it. (assuming it's small, as it should be if you're using SharedPreferences, a simple XML file...) You don't want to fault it in the future time some user clicks a button.
but whenever you call context.getSharedPreferences(...), the backing XML file is stat'd to see if it's changed, so you'll want to avoid those stats during UI events anyway. A stat should normally be fast (and often cached), but yaffs doesn't have much in the way of concurrency (and a lot of Android devices run on yaffs... Droid, Nexus One, etc.) so if you avoid disk, you avoid getting stuck behind other in-flight or pending disk operations.
so you'll probably want to load the SharedPreferences during your onCreate() and re-use the same instance, avoiding the stat.
but if you don't need your preferences anyway during onCreate(), that loading time is stalling your app's start-up unnecessarily, so it's generally better to have something like a FutureTask<SharedPreferences> subclass that kicks off a new thread to .set() the FutureTask subclasses's value. Then just lookup your FutureTask<SharedPreferences>'s member whenever you need it and .get() it. I plan to make this free behind the scenes in Honeycomb, transparently. I'll try to release some sample code which
shows best practices in this area.
Check the Android Developers blog for upcoming posts on StrictMode-related subjects in the coming week(s).
Accessing the shared preferences can take quite some time because they are read from flash storage. Do you read a lot? Maybe you could use a different format then, e.g. a SQLite database.
But don't fix everything you find using StrictMode. Or to quote the documentation:
But don't feel compelled to fix everything that StrictMode finds. In particular, many cases of disk access are often necessary during the normal activity lifecycle. Use StrictMode to find things you did by accident. Network requests on the UI thread are almost always a problem, though.
One subtlety about Brad's answer: even if you load the SharedPreferences in onCreate(), you should probably still read values on the background thread because getString() etc. block until reading the shared file preference in finishes (on a background thread):
public String getString(String key, String defValue) {
synchronized (this) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
edit() also blocks in the same way, although apply() appears to be safe on the foreground thread.
(BTW sorry to put this down here. I would have put this as a comment to Brad's answer, but I just joined and don't have enough reputation to do so.)
I know this is an old question but I want to share my approach. I had long reading times and used a combination of shared preferences and the global application class:
ApplicationClass:
public class ApplicationClass extends Application {
private LocalPreference.Filter filter;
public LocalPreference.Filter getFilter() {
return filter;
}
public void setFilter(LocalPreference.Filter filter) {
this.filter = filter;
}
}
LocalPreference:
public class LocalPreference {
public static void saveLocalPreferences(Activity activity, int maxDistance, int minAge,
int maxAge, boolean showMale, boolean showFemale) {
Filter filter = new Filter();
filter.setMaxDistance(maxDistance);
filter.setMinAge(minAge);
filter.setMaxAge(maxAge);
filter.setShowMale(showMale);
filter.setShowFemale(showFemale);
BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
babysitApplication.setFilter(filter);
SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
securePreferences.edit().putInt(Preference.FILER_MAX_DISTANCE.toString(), maxDistance).apply();
securePreferences.edit().putInt(Preference.FILER_MIN_AGE.toString(), minAge).apply();
securePreferences.edit().putInt(Preference.FILER_MAX_AGE.toString(), maxAge).apply();
securePreferences.edit().putBoolean(Preference.FILER_SHOW_MALE.toString(), showMale).apply();
securePreferences.edit().putBoolean(Preference.FILER_SHOW_FEMALE.toString(), showFemale).apply();
}
public static Filter getLocalPreferences(Activity activity) {
BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
Filter applicationFilter = babysitApplication.getFilter();
if (applicationFilter != null) {
return applicationFilter;
} else {
Filter filter = new Filter();
SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
filter.setMaxDistance(securePreferences.getInt(Preference.FILER_MAX_DISTANCE.toString(), 20));
filter.setMinAge(securePreferences.getInt(Preference.FILER_MIN_AGE.toString(), 15));
filter.setMaxAge(securePreferences.getInt(Preference.FILER_MAX_AGE.toString(), 50));
filter.setShowMale(securePreferences.getBoolean(Preference.FILER_SHOW_MALE.toString(), true));
filter.setShowFemale(securePreferences.getBoolean(Preference.FILER_SHOW_FEMALE.toString(), true));
babysitApplication.setFilter(filter);
return filter;
}
}
public static class Filter {
private int maxDistance;
private int minAge;
private int maxAge;
private boolean showMale;
private boolean showFemale;
public int getMaxDistance() {
return maxDistance;
}
public void setMaxDistance(int maxDistance) {
this.maxDistance = maxDistance;
}
public int getMinAge() {
return minAge;
}
public void setMinAge(int minAge) {
this.minAge = minAge;
}
public int getMaxAge() {
return maxAge;
}
public void setMaxAge(int maxAge) {
this.maxAge = maxAge;
}
public boolean isShowMale() {
return showMale;
}
public void setShowMale(boolean showMale) {
this.showMale = showMale;
}
public boolean isShowFemale() {
return showFemale;
}
public void setShowFemale(boolean showFemale) {
this.showFemale = showFemale;
}
}
}
MainActivity (activity that get called first in your application):
LocalPreference.getLocalPreferences(this);
Steps explained:
The main activity calls getLocalPreferences(this) -> this will read your preferences, set the filter object in your application class and returns it.
When you call the getLocalPreferences() function again somewhere else in the application it first checks if it's not available in the application class which is a lot faster.
NOTE: ALWAYS check if an application wide variable is different from NULL, reason -> http://www.developerphil.com/dont-store-data-in-the-application-object/
The application object will not 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.
If I didn't check on null I would allow a nullpointer to be thrown when calling for example getMaxDistance() on the filter object (if the application object was swiped from the memory by Android)
SharedPreferences class does some reads & writes within XML files on disk, so just like any other IO operation it could be blocking. The amount of data currently stored in SharedPreferences affects the time and resource consumed by the API calls. For minimal amounts of data it's a matter of a few milliseconds (sometimes even less than a millisecond) to get/put data. But from the point of view of an expert it could be important to improve the performance by doing the API calls in background. For an asynchronous SharedPreferences I suggest checking out the Datum library.
i do not see any reason to read them from a background thread. but to write it i would. at startup time the shared preference file is loaded into memory so its fast to access, but to write things can take a bit of time so we can use apply the write async. that should be the difference between commit and apply methods of shared prefs.