The answer is at the bottom.
I've added a modification to the 4.0.4 browser which adds a new preferences section. When the browser is launched, the value is read in correctly and seems to get cached. I added a log event any time getMaxTabs is used by Controller.java and it's only read on first launch. If I change the setting nothing happens. But if I kill the browser and re-launch after changing the setting, the new value is read in correctly. Is there a way to force this setting to be re-cached on change?
I am looking at SharedPreferences.OnSharedPreferenceChangeListener but I don't see HOW to have that variable re-read forcibly.
int getMaxTabs() {
Context mContext = mActivity.getApplicationContext();
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
String numTabs = mPrefs.getString(PreferenceKeys.PREF_MAX_TABS, "20");
int t = Integer.parseInt(numTabs);
Log.i("max open tabs", "max: " + t);
return t;
// The original content of getMaxTabs is below
// return mActivity.getResources().getInteger(R.integer.max_tabs);
}
So the log item only appears the first time the browser is launched. The new preference is set because if I change it and kill/relaunch the browser, the new value is reflected. I need to either not cache this setting or force the cache to be refreshed on change, which would be more efficient.
src/com/android/browser/preferences/TabsPreferencesFragment.java
package com.android.browser.preferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import com.android.browser.BrowserSettings;
import com.android.browser.PreferenceKeys;
import com.android.browser.R;
import com.android.browser.search.SearchEngine;
public class TabsPreferencesFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the XML preferences file
addPreferencesFromResource(R.xml.tabs_preferences);
}
}
res/xml/tabs_preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android" >
<ListPreference
android:key="max_open_tabs"
android:defaultValue="20"
android:title="#string/pref_max_open_tabs"
android:entries="#array/pref_max_open_tabs_choices"
android:entryValues="#array/pref_max_open_tabs_values"
/>
</PreferenceScreen>
This is how it is called in src/com/android/browser/TabControl.java
private int mMaxTabs;
TabControl(Controller controller) {
mController = controller;
mMaxTabs = mController.getMaxTabs();
mTabs = new ArrayList<Tab>(mMaxTabs);
mTabQueue = new ArrayList<Tab>(mMaxTabs);
}
boolean canCreateNewTab() {
return mMaxTabs > mTabs.size();
}
SOLUTION
The answer lies here, in packages/apps/Browser/src/com/android/browser/TabControl.java
Around line 55 we see:
/**
* Construct a new TabControl object
*/
TabControl(Controller controller) {
mController = controller;
mMaxTabs = mController.getMaxTabs();
mTabs = new ArrayList<Tab>(mMaxTabs);
mTabQueue = new ArrayList<Tab>(mMaxTabs);
}
The TabControl object is constructed one time at browser launch. Here we define mMaxTabs by calling mController.getMaxTabs().
Around line 155 canCreateNewTab is defined, using mMaxTabs.
boolean canCreateNewTab() {
return mMaxTabs > mTabs.size();
}
Since mMaxTabs is a static value it will never change on preference update.
The solution is this.
/**
* Construct a new TabControl object
*/
TabControl(Controller controller) {
mController = controller;
mTabs = new ArrayList<Tab>(mMaxTabs);
mTabQueue = new ArrayList<Tab>(mMaxTabs);
}
boolean canCreateNewTab() {
return mController.getMaxTabs() > mTabs.size();
}
Now every time a new browser tab is requested, the pref setting is verified.
Related
This question already has answers here:
Determine if Android app is being used for the first time
(15 answers)
Closed 6 years ago.
I am new to android development and and I want to setup some of application's attributes based on Application first run after installation. Is there any way to find that the application is running for the first time and then to setup its first run attributes?
The following is an example of using SharedPreferences to achieve a 'first run' check.
public class MyActivity extends Activity {
SharedPreferences prefs = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Perhaps set content view here
prefs = getSharedPreferences("com.mycompany.myAppName", MODE_PRIVATE);
}
#Override
protected void onResume() {
super.onResume();
if (prefs.getBoolean("firstrun", true)) {
// Do first run stuff here then set 'firstrun' as false
// using the following line to edit/commit prefs
prefs.edit().putBoolean("firstrun", false).commit();
}
}
}
When the code runs prefs.getBoolean(...) if there isn't a boolean saved in SharedPreferences with the key "firstrun" then that indicates the app has never been run (because nothing has ever saved a boolean with that key or the user has cleared the app data in order to force a 'first run' scenario). If this isn't the first run then the line prefs.edit().putBoolean("firstrun", false).commit(); will have been executed and therefore prefs.getBoolean("firstrun", true) will actually return false as it overrides the default true provided as the second parameter.
The accepted answer doesn't differentiate between a first run and subsequent upgrades. Just setting a boolean in shared preferences will only tell you if it is the first run after the app is first installed. Later if you want to upgrade your app and make some changes on the first run of that upgrade, you won't be able to use that boolean any more because shared preferences are saved across upgrades.
This method uses shared preferences to save the version code rather than a boolean.
import com.yourpackage.BuildConfig;
...
private void checkFirstRun() {
final String PREFS_NAME = "MyPrefsFile";
final String PREF_VERSION_CODE_KEY = "version_code";
final int DOESNT_EXIST = -1;
// Get current version code
int currentVersionCode = BuildConfig.VERSION_CODE;
// Get saved version code
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
int savedVersionCode = prefs.getInt(PREF_VERSION_CODE_KEY, DOESNT_EXIST);
// Check for first run or upgrade
if (currentVersionCode == savedVersionCode) {
// This is just a normal run
return;
} else if (savedVersionCode == DOESNT_EXIST) {
// TODO This is a new install (or the user cleared the shared preferences)
} else if (currentVersionCode > savedVersionCode) {
// TODO This is an upgrade
}
// Update the shared preferences with the current version code
prefs.edit().putInt(PREF_VERSION_CODE_KEY, currentVersionCode).apply();
}
You would probably call this method from onCreate in your main activity so that it is checked every time your app starts.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkFirstRun();
}
private void checkFirstRun() {
// ...
}
}
If you needed to, you could adjust the code to do specific things depending on what version the user previously had installed.
Idea came from this answer. These also helpful:
How can you get the Manifest Version number from the App's (Layout) XML variables?
User versionName value of AndroidManifest.xml in code
If you are having trouble getting the version code, see the following Q&A:
How to get the build/version number of your Android application?
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.UUID;
import android.content.Context;
public class Util {
// ===========================================================
//
// ===========================================================
private static final String INSTALLATION = "INSTALLATION";
public synchronized static boolean isFirstLaunch(Context context) {
String sID = null;
boolean launchFlag = false;
if (sID == null) {
File installation = new File(context.getFilesDir(), INSTALLATION);
try {
if (!installation.exists()) {
launchFlag = true;
writeInstallationFile(installation);
}
sID = readInstallationFile(installation);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return launchFlag;
}
private static String readInstallationFile(File installation) throws IOException {
RandomAccessFile f = new RandomAccessFile(installation, "r");// read only mode
byte[] bytes = new byte[(int) f.length()];
f.readFully(bytes);
f.close();
return new String(bytes);
}
private static void writeInstallationFile(File installation) throws IOException {
FileOutputStream out = new FileOutputStream(installation);
String id = UUID.randomUUID().toString();
out.write(id.getBytes());
out.close();
}
}
> Usage (in class extending android.app.Activity)
Util.isFirstLaunch(this);
There is no way to know that through the Android API. You have to store some flag by yourself and make it persist either in a SharedPreferenceEditor or using a database.
If you want to base some licence related stuff on this flag, I suggest you use an obfuscated preference editor provided by the LVL library. It's simple and clean.
Regards,
Stephane
I'm not sure it's good way to check it. What about case when user uses button "clear data" from settings? SharedPreferences will be cleared and you catch "first run" again. And it's a problem. I guess it's better idea to use InstallReferrerReceiver.
Just check for some preference with default value indicating that it's a first run. So if you get default value, do your initialization and set this preference to different value to indicate that the app is initialized already.
The following is an example of using SharedPreferences to achieve a 'forWhat' check.
preferences = PreferenceManager.getDefaultSharedPreferences(context);
preferencesEditor = preferences.edit();
public static boolean isFirstRun(String forWhat) {
if (preferences.getBoolean(forWhat, true)) {
preferencesEditor.putBoolean(forWhat, false).commit();
return true;
} else {
return false;
}
}
There's no reliable way to detect first run, as the shared preferences way is not always safe, the user can delete the shared preferences data from the settings!
a better way is to use the answers here Is there a unique Android device ID? to get the device's unique ID and store it somewhere in your server, so whenever the user launches the app you request the server and check if it's there in your database or it is new.
This might help you
public class FirstActivity extends Activity {
SharedPreferences sharedPreferences = null;
Editor editor;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
sharedPreferences = getSharedPreferences("com.myAppName", MODE_PRIVATE);
}
#Override
protected void onResume() {
super.onResume();
if (sharedPreferences.getBoolean("firstRun", true)) {
//You can perform anything over here. This will call only first time
editor = sharedPreferences.edit();
editor.putBoolean("firstRun", false)
editor.commit();
}
}
}
SharedPreferences mPrefs;
final String welcomeScreenShownPref = "welcomeScreenShown";
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
// second argument is the default to use if the preference can't be found
Boolean welcomeScreenShown = mPrefs.getBoolean(welcomeScreenShownPref, false);
if (!welcomeScreenShown) {
// here you can launch another activity if you like
SharedPreferences.Editor editor = mPrefs.edit();
editor.putBoolean(welcomeScreenShownPref, true);
editor.commit(); // Very important to save the preference
}
}
I have an Activity that dynamically adds two fragments. One is a hidden (no view) Fragment that has setRetainInstance(true) and handles the interface to my Database Handler. Its purpose is to start the AsyncTask for getting data out of the database and listen for the Database Handler to give its results back. It will then hand the data back to the Activity via another listener. The Activity will then hand the data to the display Fragment which has a ListView within and will display accordingly.
Activity: NOT a FRAGMENT ACTIVITY
import android.app.Activity;
import android.os.Bundle;
public class Workout_Search_Display_Activity extends Activity {
private final String search_string = "SEARCH_STRING";
private final String search_type = "SEARCH_TYPE";
private String Search_String = "";
private String Search_Type = "";
private Workout_Search_Holder_Fragment SearchHolder;
private Workout_Search_Display_Fragment search_display_fragment;
private Workout_Search_Activity_Listener WSAL;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Search_String = getIntent().getStringExtra(search_string);
Search_Type = getIntent().getStringExtra(search_type);
if (getFragmentManager().findFragmentById(android.R.id.content) == null) {
SearchHolder = Workout_Search_Holder_Fragment.newInstance(Search_String, Search_Type);
getFragmentManager().beginTransaction()
.add(android.R.id.content, SearchHolder).commit();
search_display_fragment = Workout_Search_Display_Fragment.newInstance();
getFragmentManager().beginTransaction()
.add(android.R.id.content, search_display_fragment).commit;
} //added too try to fix// else
//added to try to fix// SearchHolder = (Workout_Search_Holder_Fragment) getFragmentManager.findFragmentByTag("search_holder");
WSAL = new Workout_Search_Activity_Listener() {
public void NothingFound() {
search_display_fragment.no_data();
}
public void results_found(ArrayList<Search_Results_Holder> results) {
search_display_fragment.is_data();
search_display_fragment.handover_data(results);
}
};
SearchHolder.setListener(WSAL);
}
}
Fragment:
import android.app.Fragment;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
public class Workout_Search_Holder_Fragment extends Fragment implements DatabaseHelper.Workout_Search_Listener {
private String Search_String = "";
private String Search_Type = "";
private final static String search_string = "SEARCH_STRING";
private final static String search_type = "SEARCH_TYPE";
private Workout_Search_Activity_Listener listener;
protected static Workout_Search_Holder_Fragment newInstance(
String Search_String, String Search_Type) {
Workout_Search_Holder_Fragment f = new Workout_Search_Holder_Fragment();
Bundle args = new Bundle();
args.putString(search_string, Search_String);
args.putString(search_type, Search_Type);
f.setArguments(args);
return f;
}
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(true);
Search_String = getArguments().getString(search_string, null);
Search_Type = getArguments().getString(search_type, null);
sendSearch();
}
public Workout_Search_Activity_Listener getListener() {
return listener;
}
public void setListener(Workout_Search_Activity_Listener listener) {
this.listener = listener;
}
private void sendSearch() {
DatabaseHelper.getInstance(getActivity()).getSearchResultsAsync(
Search_String, Search_Type, this);
}
static public <T> void executeAsyncTask(AsyncTask<T, ?, ?> task, T... params) {
if (Build.VERSION.SDK_INT > +Build.VERSION_CODES.HONEYCOMB) {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
} else {
task.execute(params);
}
}
#Override
public void return_no_results_found() {
listener.NothingFound();
}
#Override
public void return_search_results(ArrayList<Search_Results_Holder> results) {
Log.v("workout search holder fragment", "results found in fragment, handing off to activity");
listener.results_found(results);
}
}
My issue is: When rotating the screen, my code crashes with a Null Pointer Exception on the SetListener for the Search_Hander in the Activity. If I change the Activity to a FragmentActivity and use the SupportFragmentManager....none of this is an issue...all works correctly with just those quick changes (something about the now-deprecated onRetainNonConfigurationInstance() being overridden by the SupportFragmentManager and it handles everything for you).
In trying to fix this, I kept it as an Activity, but put in SearchHolder = (Workout_Search_Holder_Fragment) getFragmentManager.findFragmentByTag("search_holder") as part of the else if the R.id.Content wasn't null and gave the Fragment a tag when I initially set it up if the R.id.Content WAS null. See the commented out code above. This worked, but created another issue where the onActivityCreated started again and launched my AsyncTask when I didn't want it to. I can't send or check variables in the savedInstanceState bundle to the fragment, as it is null due to the setRetainInstance(true). I know I'm doing something wrong, but can't get around it.
You are using
add(ANDROID.R.id.content, SearchHolder)
while adding your fragment, instead try
add(ContainerID, SearchHolder,"search_holder"), which makes sure that your fragment is added with a tag "search_holder"
then as you did in else part try getting that fragment object by calling
findFragmentByTag()
method
Hope this helps!!!
Answer is two fold:
I needed to manually attach the Fragments, this I knew.
I was executing my AsyncTask in the wrong area. This I didn't know.
It turns out that my AsyncTask was re-executing as OnActivityCreated is called again, even if saveInstanceState(true) is set. BUT, onCreate is not. By just changing the calling method to onCreate, i ensured that the AsyncTask was called only once, as it won't be called again upon Activity recreation. In the Activity, I still needed to reset my holders for both fragments upon testing if R.id.content was NOT null by grabbing those fragments that matched the tags...and this was part of the answer that Dinash gave.
if (getFragmentManager().findFragmentById(android.R.id.content) == null) {
SearchHolder = Workout_Search_Holder_Fragment.newInstance(
Search_String, Search_Type, false);
getFragmentManager().beginTransaction()
.add(android.R.id.content, SearchHolder, "searchholder")
.commit();
search_display_fragment = Workout_Search_Display_Fragment
.newInstance();
getFragmentManager()
.beginTransaction()
.add(android.R.id.content, search_display_fragment,
"displayholder").commit();
} else {
Log.v("activity", "non-null holder");
SearchHolder = (Workout_Search_Holder_Fragment) getFragmentManager()
.findFragmentByTag("searchholder");
search_display_fragment = (Workout_Search_Display_Fragment) getFragmentManager()
.findFragmentByTag("displayholder");
}
FragmentManager required me to test for the content being null in addition to reattaching the Fragments on my own, which is something that the FragmentSupportManager did NOT required. This also has the effect of requireing me to save the ArrayList that I am using for the ListView...again...FragmentSupportManager did all this on its own without prodding from me.
I still am curious as to why this behavior is different for the FragmentActivity as it seems to take care of ALL of this for me and required minimum amount of effort/code to work.
I have a OnPreferenceClickListener which should remove a specific Preference (with key preference_to_remove) from a PreferenceScreen.
The problem is that my solution works when preference_to_remove is not located inside a nested PreferenceScreen but does not work, when it is inside a nested screen AND the screen orientation changes. Before screen orientation changes, nested screens are working as expected, too.
The following code contains two version, one with a flat non-nested PreferenceScreen and the broken nested PreferenceScreen.
What is the reason that the nested version is not able to remove the Preference with key preference_to_remove after screen orientation changes? What would be a solution besides using only flat PreferenceScreens and Intents to start new PreferenceScreens as pseudo children?
PS: I am using PreferenceActivity for FroYo compatibility.
How to reproduce with Test-App
Open App → Click Flat-Button → Click preference_to_click which should remove preference_to_remove. → Orientation change → Click preference_to_click to remove preference_to_remove again. Preference removed? Success!
Open App → Click Subscreen-Button → Click Test → Now repeat the steps from the first test, but this time preference_to_remove will be not not removable after orientation changing.
Download App (Source)
pref_flat.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<Preference
android:key="preference_to_click"
android:persistent="false"
android:title="preference_to_click" />
<Preference
android:key="preference_to_remove"
android:title="preference_to_remove" />
</PreferenceScreen>
pref_subscreen.xml (Nested PreferenceScreen)
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<PreferenceScreen
android:key="subscreen"
android:persistent="false"
android:title="Test" >
<Preference
android:key="preference_to_click"
android:persistent="false"
android:title="preference_to_click" />
<Preference
android:key="preference_to_remove"
android:title="preference_to_remove" />
</PreferenceScreen>
</PreferenceScreen>
Diff of PrefFlatActivity.java and PrefSubscreenActivity.java
1c1
< public class PrefFlatActivity extends PreferenceActivity {
---
> public class PrefSubscreenActivity extends PreferenceActivity {
5,6c5,7
< public static final String PREFERENCE_TO_CLICK = "preference_to_click";
< public static final String PREFERENCE_TO_REMOVE = "preference_to_remove";
---
> private static final String PREFERENCE_TO_CLICK = PrefFlatActivity.PREFERENCE_TO_CLICK;
> private static final String PREFERENCE_TO_REMOVE = PrefFlatActivity.PREFERENCE_TO_REMOVE;
> private static final String PREFERENCE_SUBSCREEN = "subscreen";
15c16
< addPreferencesFromResource(R.xml.pref_flat);
---
> addPreferencesFromResource(R.xml.pref_subscreen);
28c29
< PreferenceScreen screen = getPreferenceScreen();
---
> PreferenceScreen screen = (PreferenceScreen) findPreference(PREFERENCE_SUBSCREEN);
PrefFlatActivity.java (Working)
/**
* Works as expected. Clicking toggles the "visibility" of the PREFERENCE_TO_REMOVE Preference.
*/
public class PrefFlatActivity extends PreferenceActivity {
/**
* Preference keys.
*/
public static final String PREFERENCE_TO_CLICK = "preference_to_click";
public static final String PREFERENCE_TO_REMOVE = "preference_to_remove";
private final String PREF_NAME = getClass().getName() + ".pref";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getPreferenceManager().setSharedPreferencesName(PREF_NAME);
addPreferencesFromResource(R.xml.pref_flat);
findPreference(PREFERENCE_TO_CLICK)
.setOnPreferenceClickListener(new OnFlatClickListener());
}
/**
* Removes or adds Preference with key PREFERENCE_TO_REMOVE when clicked.
*/
private class OnFlatClickListener implements OnPreferenceClickListener {
private Preference mRescuedPreference;
public boolean onPreferenceClick(Preference preference) {
PreferenceScreen screen = getPreferenceScreen();
Preference prefToRemove = screen.findPreference(PREFERENCE_TO_REMOVE);
Log.d("test", "Found PREFERENCE_TO_REMOVE: " + (prefToRemove != null));
if (prefToRemove != null) {
screen.removePreference(prefToRemove);
mRescuedPreference = prefToRemove; // Rescue reference to re-add it later.
}
else {
screen.addPreference(mRescuedPreference);
}
return true;
}
}
}
PrefSubscreenActivity.java (Nested, broken after orientation change)
/**
* Broken after orientation change. Clicking does not remove/add PREFERENCE_TO_REMOVE.
*/
public class PrefSubscreenActivity extends PreferenceActivity {
/**
* Preference keys.
*/
private static final String PREFERENCE_TO_CLICK = PrefFlatActivity.PREFERENCE_TO_CLICK;
private static final String PREFERENCE_TO_REMOVE = PrefFlatActivity.PREFERENCE_TO_REMOVE;
private static final String PREFERENCE_SUBSCREEN = "subscreen";
private final String PREF_NAME = getClass().getName() + ".pref";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getPreferenceManager().setSharedPreferencesName(PREF_NAME);
addPreferencesFromResource(R.xml.pref_subscreen);
findPreference(PREFERENCE_TO_CLICK)
.setOnPreferenceClickListener(new OnFlatClickListener());
}
/**
* Removes or adds Preference with key PREFERENCE_TO_REMOVE when clicked.
*/
private class OnFlatClickListener implements OnPreferenceClickListener {
private Preference mRescuedPreference;
public boolean onPreferenceClick(Preference preference) {
PreferenceScreen screen = (PreferenceScreen) findPreference(PREFERENCE_SUBSCREEN);
Preference prefToRemove = screen.findPreference(PREFERENCE_TO_REMOVE);
Log.d("test", "Found PREFERENCE_TO_REMOVE: " + (prefToRemove != null));
if (prefToRemove != null) {
screen.removePreference(prefToRemove);
mRescuedPreference = prefToRemove; // Rescue reference to re-add it later.
}
else {
screen.addPreference(mRescuedPreference);
}
return true;
}
}
}
edit: I've been having trouble getting this to work as you requested. I read through the source for Preferences and how they handle state without any luck, so I'm probably missing something obvious (isn't that how it often goes?)
I did some testing, and I believe that the state of the PreferenceScreen and/or the Dialog it uses to display the nested PreferenceScreen is behaving in a way that neither of us expects. This behavior is not observed when using a PreferenceCategory instead of a PreferenceScreen, for example.
Since this way of working with Preferences is deprecated, you can always try using fragments:
http://developer.android.com/reference/android/preference/PreferenceActivity.html
However, I suspect you are avoiding this for legacy reasons.
Another option would be to create your Preference programmatically:
Building Preference screen in code depending on another setting
Or, you could handle configuration changes yourself:
http://developer.android.com/reference/android/app/Activity.html#ConfigurationChanges
https://stackoverflow.com/questions/3542333/how-to-prevent-custom-views-from-losing-state-across-screen-orientation-changes/8127813#8127813
<activity ..
android:configChanges="orientation|keyboardHidden" .. >
Hopefully you get this figured out soon. I'm sure the solution will pop out at us eventually; it can't be that hard! Here's hoping ;)
Original source (mostly) retained below:
package com.example.removepref;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.util.Log;
public class PrefSubscreenActivity extends PreferenceActivity {
/**
* Preference keys.
*/
private static final String PREFERENCE_TO_CLICK = PrefFlatActivity.PREFERENCE_TO_CLICK;
private static final String PREFERENCE_TO_REMOVE = PrefFlatActivity.PREFERENCE_TO_REMOVE;
private static final String PREFERENCE_SUBSCREEN = "subscreen";
private final String PREF_NAME = getClass().getName() + ".pref";
#Override
protected void onResume() {
super.onResume();
getPreferenceManager().setSharedPreferencesName(PREF_NAME);
addPreferencesFromResource(R.xml.pref_subscreen);
findPreference(PREFERENCE_TO_CLICK).setOnPreferenceClickListener(new OnFlatClickListener());
}
/**
* Removes or adds Preference with key PREFERENCE_TO_REMOVE when clicked.
*/
private class OnFlatClickListener implements OnPreferenceClickListener {
private Preference mRescuedPreference;
private boolean mPrefToRemoveVisible = true;
public boolean onPreferenceClick(Preference preference) {
// toggle visibility
mPrefToRemoveVisible = !mPrefToRemoveVisible;
PreferenceScreen screen = (PreferenceScreen) findPreference(PREFERENCE_SUBSCREEN);
Preference prefToRemove = screen.findPreference(PREFERENCE_TO_REMOVE);
Log.d("test", "Found PREFERENCE_TO_REMOVE: " + (prefToRemove != null));
Log.d("test", "And noted mPrefToRemoveVisible: " + mPrefToRemoveVisible);
//Replaced the conditional blocks:
if (mPrefToRemoveVisible && null == prefToRemove) {
boolean added = screen.addPreference(mRescuedPreference);
Log.d("test", "screen.addPreference(mRescuedPreference) success?" + added);
} else if (!mPrefToRemoveVisible && null != prefToRemove) {
mRescuedPreference = prefToRemove;
boolean removed = screen.removePreference(prefToRemove);
Log.d("test", "screen.removePreference(mRescuedPreference) success?" + removed);
}
return true;
}
}
}
Recommended Reading: http://developer.android.com/guide/topics/resources/runtime-changes.html
I've got the following code (it's not the full code, but the rest doesn't matter). I'm trying to set the boolean "ignoreLength" to either true or false based on what the user picks in an Alertdialog. However, when the code is like this, I get this error:
"Cannot refer to a non-final variable ignoreLength inside an inner class defined in a different method"
When I make it final, it changes to this:
"The final local variable ignoreLength cannot be assigned, since it is defined in an enclosing type"
How can I make it so I can change ignoreLength?
package com.grawl.passgen;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class PassGenActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
// Interface -- Default
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Interface -- Custom
final Button button_generate = (Button) findViewById(R.id.button_generate);
final EditText text_pass = (EditText) findViewById(R.id.textPassWord);
final EditText edit_length = (EditText) findViewById(R.id.editLength);
// Set up Arrays
final String[] lowerCase;
final String[] upperCase;
final String[] numbers;
final String[] symbols;
// Fill Arrays
createArray characters = new createArray();
lowerCase = characters.getArrayLower();
upperCase = characters.getArrayUpper();
numbers = characters.getArrayNumbers();
symbols = characters.getArraySymbols();
// Pressing the button WOOOSH!
button_generate.setOnClickListener(new View.OnClickListener() {
**boolean ignoreLength = false;**
public void onClick(View v) {
// Set up parameters
boolean lowerCaseEnabled = true; // needs interface option
boolean upperCaseEnabled = true; // needs interface option
boolean numbersEnabled = true; // needs interface option
boolean symbolsEnabled = true; // needs interface option
// Set up length based on input from EditText
int length = 0;
try {
length = Integer.parseInt(edit_length.getText().toString());
} catch(NumberFormatException nfe) {
Toast.makeText(PassGenActivity.this, "Can't parse " + nfe, Toast.LENGTH_LONG).show();
}
if (length < 1) {
length = 1;
edit_length.setText("1");
Toast.makeText(PassGenActivity.this, "Password length can't be less than 1, set it to 1.", Toast.LENGTH_LONG).show();
};
if (length > 100) {
AlertDialog.Builder alert = new AlertDialog.Builder(PassGenActivity.this);
alert.setTitle("Warning");
alert.setMessage("You are trying to create quite a long password, are you sure?");
alert.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
ignoreLength = true;
}
});
alert.setNegativeButton("No", null);
alert.show();
}
Password password = new Password();
password.fillPassword(lowerCase, upperCase, numbers, symbols);
// Generate password
password.setPassword(lowerCaseEnabled, upperCaseEnabled, numbersEnabled, symbolsEnabled, length);
text_pass.setText(password.getPassword());
}
});
}
}
Firstly as per your question.
"Can't change final variable inside AlertDialog" is wrong.
As making you clear that a final variable can never be changed as it's final and already declared at the time of creation.
Also in you case boolean ignoreLength = false; declare the variable outside and before the click listener and do not make it final. As you need to update ignoreLength value in future.
You can't reassign local variables in java from an inner type. More here.
There are two ways to solve your problem.
1) Make ignore case a field on something--you could actually use the anonymous OnClickListener type, and give it the ignoreLength field, but I think generally you would want it to be a field on the top level class
2)
<superuglyhack>
final boolean[] ignoreLengthHolder= new boolean[]{ false };
...
ignoreLengthHolder[0] = true
</superuglyhack>
You could move the boolean ignoreLength; declaration outside of the onClick to make it a member variable of the anonymous OnClickListener class.
Also, you could put the variable in something that holds it (like an array with one item) and update it that way. ignoreLength[0] = true;
I'm still struggling to find a clean easy way to load some values in my onCreate method and pass them to another class in order that a game may load and save option settings.
I can successfully retrieve and change values from my other class but the problem is, no matter what I try, variable set in the onCreate method will not carry to the rest of the code.
I take the point that by only showing snipits of the code I may have obscured the problem but my original is far to massive and sprawling to post HOWEVER it was based on a great tutorial by Martin
http://www.droidnova.com/android-3d-game-tutorial-part-i,312.html
So I've returned to first principles and just added the problematic portion to that tutorial code and my problem has been replicated. So here is the complete code:
Vortex.java **
package com.clockworkrobot.vortex;
import android.app.Activity;
import android.os.Bundle;
public class Vortex extends Activity {
private static final String LOG_TAG = Vortex.class.getSimpleName();
private VortexView _vortexView;
private float _red ; // these are the values that actually reach VortexRender class
private float _green = 1f ; // touch the screen, it will turn green.
private float _blue ; // but why don't the variable in my onCreate override these?
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_red = 1f; // The test values I want to reach my Vortex Renderer class.
_green = 0f; // to help debug. they should make the screen magenta
_blue = 1f; // Eventually these value will be loaded during onCreate
setRed(_red); // I don't think these are needed
setGreen(_green); // But since the variable within this method
setBlue(_blue); // don't appear to be reaching their target..worth a try
_vortexView = new VortexView(this);
setContentView(_vortexView);
}
public float getRed() {
return _red;
}
public void setRed(float value) {
_red = value;
}
public float getGreen() {
return _green;
}
public void setGreen(float value) {
_green = value;
}
public float getBlue() {
return _blue;
}
public void setBlue(float value) {
_blue = value;
}
}
VortexRender.java**
package com.clockworkrobot.vortex;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
public class VortexRenderer extends Activity implements GLSurfaceView.Renderer {
private static final String LOG_TAG = VortexRenderer.class.getSimpleName();
// Vortex sw = new Vortex();
private Vortex sw;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sw = new Vortex();
}
private float _red = 0f;
private float _green = 0f;
private float _blue = 0f;
#Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Do nothing special.
}
#Override
public void onSurfaceChanged(GL10 gl, int w, int h) {
gl.glViewport(0, 0, w, h);
}
#Override
public void onDrawFrame(GL10 gl) {
// define the color we want to be displayed as the "clipping wall"
gl.glClearColor(_red, _green, _blue, 1.0f);
// clear the color buffer to show the ClearColor we called above...
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
}
public void setColor(float r, float g, float b) {
_red = sw.getRed(); // Want these to grab the values from my onCreate method
_green = sw.getGreen(); // But instead it's getting the nul values
_blue = sw.getBlue(); // eg values as set above me onCreate.
// _red = r;
// _green = g;
// _blue = b;
}
}
VortexView.java**
package com.clockworkrobot.vortex;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.view.MotionEvent;
public class VortexView extends GLSurfaceView {
private static final String LOG_TAG = VortexView.class.getSimpleName();
private VortexRenderer _renderer;
public VortexView(Context context) {
super(context);
_renderer = new VortexRenderer();
setRenderer(_renderer);
}
public boolean onTouchEvent(final MotionEvent event) {
queueEvent(new Runnable() {
public void run() {
_renderer.setColor(event.getX() / getWidth(), event.getY() / getHeight(), 1.0f);
}
});
return true;
}
}
AndroidManifest.xml**
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.clockworkrobot.vortex"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="#drawable/icon" android:label="#string/app_name">
<activity android:name=".Vortex"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>
In essence.. What I require is a way to save and recover the current screen colour when the game opens and closes.
Thanks to everyone who has helped so far.
I'm still struggling to find a clean easy way to load some values in my onCreate method and pass them to another class in order that a game may load and save option settings.
Use SharedPreferences -- that is why they are there.
Anyone got any helpful ideas?
Either:
Your other class is not talking to the Activity object you think it is, or
You are not calling the code in the other class that invokes your setter, or
Your activity is being recreated (e.g., configuration change) as part of your testing, or
As #Arnout Engelen notes, you are calling the setter from multiple places and are overwriting what you want, or
Something else, since the code you have above most likely is not really the code from your app, and fake examples like this just cause problems when you go to ask people for help, because you introduce differences between what you're really running and what you're claiming you're running
UPDATE
You are creating a Vortex via new Vortex(). Vortex is an Activity. Never create activities via the constructor. You do not access activities from other activities. Your red/green/blue values need to be in some data model accessible from all your components.
Given your code and comments, I strongly encourage you to first learn Java outside of Android, then learn Android.
I had a similar problem. The solution is easy.
If the setter method is setting an int, initially it must be written as "public int...{} " instead of "public void...{} ".
Then it must return an int variable at the end with the following code line: "return a;" (where a is any int or int variable that has previously been initiated).
Then, whenever you want an int b to get a's number, simply write:
int b = new exampleClassWhereYourMethodIs().setterMethodName();
Hope I helped!