Hi I've created a settings Activity and when I am in the settings menu and press the back button of the phone i get directed to the "home screen" which is correct but when I am pressing the top arrow of the settings menu nothings happends, it seems to be just a button. Please see imgur to see which arrow i mean. Back arrow in the settings activity, press here. I've looked around in the java code and seems to have found the button? All help is appreciated!!
Here is the whole java file of the settings menu.
public class SettingsActivity extends AppCompatPreferenceActivity {
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
*/
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object value) {
String stringValue = value.toString();
if (preference instanceof RingtonePreference) {
// For ringtone preferences, look up the correct display value
// using RingtoneManager.
if (TextUtils.isEmpty(stringValue)) {
// Empty values correspond to 'silent' (no ringtone).
preference.setSummary(R.string.pref_ringtone_silent);
} else {
Ringtone ringtone = RingtoneManager.getRingtone(
preference.getContext(), Uri.parse(stringValue));
if (ringtone == null) {
// Clear the summary if there was a lookup error.
preference.setSummary(null);
} else {
// Set the summary to reflect the new ringtone display
// name.
String name = ringtone.getTitle(preference.getContext());
preference.setSummary(name);
}
}
} else {
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
}
};
/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
/**
* Binds a preference's summary to its value. More specifically, when the
* preference's value is changed, its summary (line of text below the
* preference title) is updated to reflect the value. The summary is also
* immediately updated upon calling this method. The exact display format is
* dependent on the type of preference.
*
* #see #sBindPreferenceSummaryToValueListener
*/
private static void bindPreferenceSummaryToValue(Preference preference) {
// Set the listener to watch for value changes.
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
// Trigger the listener immediately with the preference's
// current value.
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupActionBar();
}
/**
* Set up the {#link android.app.ActionBar}, if the API is available.
*/
private void setupActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
// Show the Up button in the action bar.
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
/**
* {#inheritDoc}
*/
#Override
public boolean onIsMultiPane() {
return isXLargeTablet(this);
}
/**
* {#inheritDoc}
*/
#Override
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.pref_headers, target);
}
/**
* This method stops fragment injection in malicious applications.
* Make sure to deny any unknown fragments here.
*/
protected boolean isValidFragment(String fragmentName) {
return PreferenceFragment.class.getName().equals(fragmentName)
|| GeneralPreferenceFragment.class.getName().equals(fragmentName);
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class GeneralPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_general);
setHasOptionsMenu(true);
// Bind the summaries of EditText/List/Dialog/Ringtone preferences
// to their values. When their values change, their summaries are
// updated to reflect the new value, per the Android Design
// guidelines.
bindPreferenceSummaryToValue(findPreference("example_text"));
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
startActivity(new Intent(getActivity(), SettingsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}
}
I think this is the button?
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupActionBar();
}
/**
* Set up the {#link android.app.ActionBar}, if the API is available.
*/
private void setupActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
// Show the Up button in the action bar.
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
Open AndroidManifest.xml file. You need to define parent Activity for your SettingsActivity using:
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity"/>
then in your class:
/**
* Set up the {#link android.app.ActionBar}, if the API is available.
*/
private void setupActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
// Show the Up button in the action bar.
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
}
}
if you're using already AppCompatActivity with Toolbar, check this solution:
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// add back arrow to toolbar
if (getSupportActionBar() != null){
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
If you're also want to handle this event or change the backButton icon, go to: Display Back Arrow on Toolbar Android
Edit: add to your activity class this code:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
Hope it will help
In your AndroidManifest.xml , add this in the Activity tag
<meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".HomeScreenActivity"/>
Combining the answers above
SettingsActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getFragmentManager().beginTransaction().replace(android.R.id.content, new MainSettingsFragment()).commit();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
}
return super.onOptionsItemSelected(item);
}
#Override
public void onBackPressed() {
super.onBackPressed();
this.finish();
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
}
AndroidManifest.xml
<activity android:name=".SettingsActivity"
android:label="#string/title_settings">
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity"/>
</activity>
You Should try it.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// handle arrow click here
if (item.getItemId() == android.R.id.home) {
finish(); // close this activity and return to preview activity (if there is any)`enter code here`
}
return super.onOptionsItemSelected(item);
}
Related
Currently I am working on an app which has a bottom navbar with three menu items. I had used setOnNavigationItemSelectedListener() for items being clicked. but now iam facing issue that the method has been depreciated.
App Language: Java
Issue: 'setOnNavigationItemSelectedListener(com.google.android.material.bottomnavigation.BottomNavigationView.OnNavigationItemSelectedListener)' is deprecated
Is there any way to resolve it? is is there any better alternative than setOnNavigationItemSelectedListener() method.
Its deprecated according to github sources: BottomNavigationView.setOnNavigationItemSelectedListener
In its comment you can read:
#deprecated Use {#link NavigationBarView#setOnItemSelectedListener(OnItemSelectedListener)}
* instead.
so use NavigationBarView.setOnItemSelectedListener from its base class:
/**
* Set a listener that will be notified when a navigation item is selected. This listener will
* also be notified when the currently selected item is reselected, unless an {#link
* OnItemReselectedListener} has also been set.
*
* #param listener The listener to notify
* #see #setOnItemReselectedListener(OnItemReselectedListener)
*/
public void setOnItemSelectedListener(#Nullable OnItemSelectedListener listener) {
selectedListener = listener;
}
Also see this commit
as it explains confusion about this change:
The listeners were deprecated in favor of
NavigationBarView#OnItemSelectedListener and
NavigationBarView#OnItemReselectedListener, but deprecation
documentation was never added, so it's unclear what developers should
use instead.
you can try setonItemSelectedListener. It is working same as setOnNavigationItemSelectedListener()[tested in android 11]
bnv.setOnItemSelectedListener(new NavigationBarView.OnItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
int id = item.getItemId();
switch(id){
//check id
}
return true;
}
});
Kotlin:
bnv.setOnItemSelectedListener { item ->
when (item.itemId) {
}
true
}
We can use setOnItemSelectedListener instead of setOnNavigationItemSelectedListener and
setOnItemReselectedListener instead of setOnNavigationItemReselectedListener
navView.setOnItemSelectedListener {
// do something
true
}
// In case the default menu can be the first menu
// Should set the default selected menu BETWEEN setOnItemSelectedListener and setOnItemReselectedListener.
// It will make setOnItemSelectedListener fired when you launch app.
// If you set default menu AFTER setOnItemReselectedListener.
// Then setOnItemReselectedListener will fired when you launch app
navView.selectedItemId = R.id.navigation_home
navView.setOnItemReselectedListener {
// do something
}
you can use in Kotlin
buttmNav.setOnItemSelectedListener { item ->
when (item.itemId) {
}
true
}
viewBindingMainActivity.navView.setOnItemSelectedListener { menuItem ->
if (menuItem.itemId != R.id.navigation_home) {
Add your code
false
} else {
Add your code
true
}
}
kotlin:
use setOnItemSelectedListener
bottomNavigationView.setOnItemSelectedListener { item: MenuItem ->
when (item.itemId) {
R.id. ... -> {
Add your code
true
}
else ->
true
}
public class HomeActivity extends AppCompatActivity implements NavigationBarView.OnItemSelectedListener {
BottomNavigationView bottomNavigationView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
bottomNavigationView = findViewById(R.id.bottom_nav);
bottomNavigationView.setOnItemSelectedListener(this);
displayfragment(new FragmentHome());
}
private void displayfragment(Fragment fragment) {
getSupportFragmentManager().beginTransaction().replace(R.id.content_area, fragment).commit();
}
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment;
switch (item.getItemId()) {
case R.id.nav_home:
fragment = new FragmentHome();
break;
case R.id.nav_fav:
fragment = new FavouriteFragment();
break;
case R.id.nav_set:
fragment = new FragmentSetting();
break;
default:
fragment = new FragmentHome();
break;
}
displayfragment(fragment);
return true;
}
}
I want to implement in my app Dark mode. In default I wish it been following by the system, so in Main Activity I've placed:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
It works fine, but I if user wants to change its mind and select certain option in my app menu to toggle off/on dark mode, activity is restarting and app's still following system rules. How can I change that?
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_color_mode) {
if(AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
else
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_YES);
return true;
}
Code responsible for option you mentioned, is within onCreate(). Mechanism that allows user to change mode is not within onCreate()
public class MainActivityextends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
}
}
When you explicitly change the dark mode, Android recreates the activity and hence calls onCreate again.
So, after you change the dark mode you won't notice a change, as AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM is called again when onCreate is called by the system.
To make this works you can save a value into SharedPreference that can be checked in onCreate before setting the system dark mode.
This can be a boolean that you can toggle when you want to manually change the dark mode.
Here is a sample
public class MainActivityextends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean isSystem = prefs.getBoolean("IS_SYSTEM", true);
if (isSystem) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_color_mode) {
if(AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
else
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_YES);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("IS_SYSTEM", false).apply();
return true;
}
}
UPDATE
that works perfect, but when I quit application and then launch again, default system mode is active although I've switched it. Is possible here to make it works in that way?
You can use another SharedPreference boolean to be saved permanently
public class MainActivityextends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean isSystem = prefs.getBoolean("IS_SYSTEM", true);
boolean isNight = prefs.getBoolean("IS_NIGHT", false);
if (isSystem) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
} else if (isNight) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_color_mode) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
prefs.edit().putBoolean("IS_NIGHT", false).apply();
} else {
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_YES);
prefs.edit().putBoolean("IS_NIGHT", true).apply();
}
prefs.edit().putBoolean("IS_SYSTEM", false).apply();
return true;
}
}
I'm using two buttons with the same id in two different layouts in my app where when the first one is clicked, the app loads the 2nd layout and when the button with the same id in the 2nd layout gets clicked, it loads the first layout file. However, my issue is that this toggling happens only once and after that the button doesn't do anything. Do you have any idea on how i can call these onClickListeners whenever each button is clicked until the user leaves that activity?
CardViewActivity.java:
public class CardViewActivity extends AppCompatActivity {
private ImageView cardArtImageView;
private TextView leaderSkillDescText;
private TextView superAttackTitleText;
private TextView superAttackDescText;
private TextView passiveSkillTitleText;
private TextView passiveSkillDescText;
private TextView hpText;
private TextView attText;
private TextView defText;
private TextView costText;
private Button arrowButton;
private int selectedItemPosition;
private boolean isBtnClicked = false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.cardview_refined);
// Retrieving the data sent over from MainActivity
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
if (bundle != null) {
selectedItemPosition = bundle.getInt("Card Index");
}
//Toast.makeText(this, "WIDTH: " + SCREEN_WIDTH, Toast.LENGTH_SHORT).show();
// Initializing our views
cardArtImageView = findViewById(R.id.cardArtImageView);
viewDefinitions(false);
setSelectedViewsInit();
initCardViewData(selectedItemPosition);
arrowButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
isBtnClicked = !isBtnClicked;
if (isBtnClicked) {
setContentView(R.layout.cardview_expand_details);
viewDefinitions(true);
initCardViewData(selectedItemPosition);
setSelectedViewsInit();
Log.d("BTN", "Btn Clicked 1st time");
arrowButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
setContentView(R.layout.cardview_refined);
cardArtImageView = findViewById(R.id.cardArtImageView);
viewDefinitions(false);
initCardViewData(selectedItemPosition);
setSelectedViewsInit();
isBtnClicked = !isBtnClicked;
Log.d("BTN", "Btn Clicked 2nd time");
}
});
}
}
});
}
/**
* Sets the required textViews as selected to allow automatic scrolling
*/
private void setSelectedViewsInit() {
leaderSkillDescText.setSelected(true);
superAttackTitleText.setSelected(true);
superAttackDescText.setSelected(true);
if (passiveSkillTitleText != null && passiveSkillDescText != null) {
passiveSkillTitleText.setSelected(true);
passiveSkillDescText.setSelected(true);
}
}
/**
* Adds the views's definitions
*
* #param initPassiveInfo used to decide whether or not the passiveSkillDesc & ..Title != null
* so that they can be defined
*/
private void viewDefinitions(boolean initPassiveInfo) {
leaderSkillDescText = findViewById(R.id.leaderSkillDesc);
superAttackTitleText = findViewById(R.id.superAttackTitle);
superAttackDescText = findViewById(R.id.superAttackDesc);
if (initPassiveInfo) {
passiveSkillTitleText = findViewById(R.id.passiveSkillTitle);
passiveSkillDescText = findViewById(R.id.passiveSkillDesc);
} else {
Log.d("Definitions", "Passive info == null");
}
hpText = findViewById(R.id.HP);
attText = findViewById(R.id.ATT);
defText = findViewById(R.id.DEF);
costText = findViewById(R.id.COST);
arrowButton = findViewById(R.id.arrowButton);
}
/**
* Initialize the cardViewActivity's views with the data from the CardInfoDatabase.java class
*
* #param selectedItemPosition Used to initialize this activity's views if the intent was called from the MainScreen Fragment
*/
private void initCardViewData(int selectedItemPosition) {
if (cardArtImageView != null) {
cardArtImageView.setImageResource(CardInfoDatabase.cardArts[selectedItemPosition]);
}
leaderSkillDescText.setText(CardInfoDatabase.leaderSkills[selectedItemPosition]);
superAttackTitleText.setText(CardInfoDatabase.superAttacksName[selectedItemPosition]);
superAttackDescText.setText(CardInfoDatabase.superAttacksDesc[selectedItemPosition]);
if (passiveSkillTitleText != null && passiveSkillDescText != null) {
passiveSkillTitleText.setText(CardInfoDatabase.passiveSkillsName[selectedItemPosition]);
passiveSkillDescText.setText(CardInfoDatabase.passiveSkillsDesc[selectedItemPosition]);
}
hpText.setText(CardInfoDatabase.hp[selectedItemPosition].toString());
attText.setText(CardInfoDatabase.att[selectedItemPosition].toString());
defText.setText(CardInfoDatabase.def[selectedItemPosition].toString());
costText.setText(CardInfoDatabase.cost[selectedItemPosition].toString());
}
}
To avoid this issue, you need to make sure that the OnClickListener you assign to the button always sets the OnClickListener for the button in the "new" layout.
I haven't tested this, but it seems like it should work in theory. Try defining the listener as a private member of your class, then setting it in your onCreate, like arrowButton.setOnClickListener(arrowClickListener);:
private void arrowClickListener = new View.OnClickListener(){
#Override
public void onClick(View view) {
// clicked buttton -- pick layout based on button "state"
int resId = isBtnClicked ? R.layout.cardview_expand_details : R.layout.cardview_refined;
// set the contentview with the layout we determined earlier
setContentView(resId);
// If we're in the "normal" view, find the card art view and set our field to it
if (!isBtnClicked){
cardArtImageView = findViewById(R.id.cardArtImageView);
}
// do other initialization stuff
viewDefinitions(isBtnClicked);
initCardViewData(selectedItemPosition);
setSelectedViewsInit();
// set our new arrow button click listener to this listener
arrowButton.setOnClickListener(arrowClickListener);
// toggle button flag
isBtnClicked = !isBtnClicked;
}
}
Sorry if I got some of the logic wrong -- the key in this case is to set the click listener "recursively", in a manner of speaking, which ensures that a listener gets set after every click.
I have Preferences activity in my app that has ListPreference so the user can choose language for the app.
The app displays the new language just after the user close the Preferences activity.
I want to create a listener to the ListPreference so the app will restart when the Listener is triggers (just after the user choose a language/choose item from the ListPreference).
How can I do that?
SettingsActivity:
public class SettingsActivity extends AppCompatPreferenceActivity {
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
*/
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object value) {
String stringValue = value.toString();
if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);
// Set the summary to reflect the new value.
preference.setSummary(
index >= 0
? listPreference.getEntries()[index]
: null);
} else {
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
}
};
/**
* Binds a preference's summary to its value. More specifically, when the
* preference's value is changed, its summary (line of text below the
* preference title) is updated to reflect the value. The summary is also
* immediately updated upon calling this method. The exact display format is
* dependent on the type of preference.
*
* #see #sBindPreferenceSummaryToValueListener
*/
private static void bindPreferenceSummaryToValue(Preference preference) {
// Set the listener to watch for value changes.
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
// Trigger the listener immediately with the preference's
// current value.
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupActionBar();
setTitle(R.string.action_settings);
}
/**
* Set up the {#link android.app.ActionBar}, if the API is available.
*/
private void setupActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
// Show the Up button in the action bar.
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
#Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
if (!super.onMenuItemSelected(featureId, item)) {
NavUtils.navigateUpFromSameTask(this);
}
return true;
}
return super.onMenuItemSelected(featureId, item);
}
/**
* This method stops fragment injection in malicious applications.
* Make sure to deny any unknown fragments here.
*/
protected boolean isValidFragment(String fragmentName) {
return PreferenceFragment.class.getName().equals(fragmentName)
|| GeneralPreferenceFragment.class.getName().equals(fragmentName);
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class GeneralPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_general);
setHasOptionsMenu(true);
// Bind the summaries of EditText/List/Dialog/Ringtone preferences
// to their values. When their values change, their summaries are
// updated to reflect the new value, per the Android Design
// guidelines.
bindPreferenceSummaryToValue(findPreference("example_text"));
bindPreferenceSummaryToValue(findPreference(getString(R.string.language_shared_pref_key)));
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
Intent intent = new Intent(getActivity(), MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
getActivity().finish();
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
}
}
pref_general.xml:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<ListPreference
android:defaultValue="#string/language_code"
android:entries="#array/pref_languages_list_titles"
android:entryValues="#array/pref_languages_list_values"
android:key="#string/language_shared_pref_key"
android:negativeButtonText="#null"
android:positiveButtonText="#null"
android:title="#string/pref_title_language" />
</PreferenceScreen>
Thank you!!!
Here is some real quick sample code for a shared prefs chaneg listener I have set-up in one of my projects; it's located in the onCreate of a Service but obviously can detect changes to my shared prefs that originate from anywhere in my app.
private SharedPreferences.OnSharedPreferenceChangeListener listener;
//Loads Shared preferences
prefs = PreferenceManager.getDefaultSharedPreferences(this);
//Setup a shared preference listener for hpwAddress and restart transport
listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (key.equals(/*key for shared pref you're listening for*/) {
//Do stuff; restart activity in your case
}
};
prefs.registerOnSharedPreferenceChangeListener(listener);
One possible solution is to keep everything together, and implement the OnSharedPreferenceChangeListener interface inside your own GeneralPreferenceFragment.
You can do that by declaring that GeneralPreferenceFragment both inherits from PreferenceFragment, and implements SharedPreferences.OnSharedPreferenceChangeListener interface. This means that you need to define override for onSharedPreferenceChanged in GeneralPreferenceFragment.
Because preferences can change only if the fragment in question is active, you can register your fragment as listener in onResume for a fragment (via registerOnSharedPreferenceChangeListener), and unregister it on onStop.
The following example code is in Kotlin, not in Java, but it should be fairly easy to translate it.
class SettingsActivity : AppCompatActivity() {
// ...
class SettingsFragment : PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener
{
// ...
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?,
key: String?) {
if (key == "<something>")
// do something
}
override fun onResume() {
super.onResume()
preferenceScreen.sharedPreferences
?.registerOnSharedPreferenceChangeListener(this)
}
override fun onPause() {
super.onPause()
preferenceScreen.sharedPreferences
?.unregisterOnSharedPreferenceChangeListener(this)
}
}
I have an Activity named PhotoSelectorActivity. It inherits from a BaseActivity that looks like this:
public class BaseActivity
extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(this.getClass().getSimpleName(),
"onCreate("+Integer.toHexString(System.identityHashCode(this))+")");
}
#Override
protected void onDestroy() {
super.onDestroy();
getSupportActionBar().setCustomView(null);
Log.d(this.getClass().getSimpleName(),
"onDestroy("+Integer.toHexString(System.identityHashCode(this))+")");
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
return onNavigateUp(item);
case R.id.menu_item_settings:
startActivity(new Intent(this, PreferencesActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
protected ActionBar setupActionBar(boolean enableBackButton) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
getSupportActionBar().setDisplayHomeAsUpEnabled(enableBackButton);
}
ActionBar actionBar = getSupportActionBar();
actionBar.setCustomView(R.layout.action_bar);
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowCustomEnabled(true);
return null;
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
The purpose of this BaseActivity is to provide the same menu and actionbar to each one of my activities. You'll notice the getSupportActionBar().setCustomView(null) in the onDestroy() method, that's there to try and combat the problem that I may be having.
When i get an orientation change event, i notice in DDMS that i end up with 2 instances of my activity. One of them may be leaking, but I'm not certain. Here's a screen shot from DDMS:
So the object at the top is the Activity in question: PhotoSelectorActivity. The instance shown here is the previous instance (onDestroy() has already been called on it). Yet it remains in memory even after a forced GC via DDMS.
Another bit of information is that this only seems to happen after using a dialog. That is, when the Activity is initially displayed and before the user performs and action I can do back to back orientation changes without the # of activities climbing above 1. After I've used the following dialog i seem to get the extra Activity in memory:
public class PhotoSourceDialog
extends DialogFragment
implements DialogInterface.OnClickListener {
public static interface PhotoSourceDialogListener {
void onPhotoSourceSelected(String result);
}
private PhotoSourceDialogListener listener;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!PhotoSourceDialogListener.class.isInstance(activity)) {
throw new IllegalStateException(
"Activity must implement PhotoSourceDialogListener");
}
listener = PhotoSourceDialogListener.class.cast(activity);
}
#Override
public void onDetach() {
super.onDetach();
listener = null;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.photo_source)
.setItems(R.array.photo_sources, this).create();
}
#Override
public void onClick(DialogInterface dialog, int which) {
String choice = getResources().getStringArray(
R.array.photo_sources)[which];
if (listener!=null) {
listener.onPhotoSourceSelected(choice);
}
}
}
and to invoke it i do this in my activity:
PhotoSourceDialog dialog = new PhotoSourceDialog();
dialog.show(getSupportFragmentManager(), PhotoSourceDialog.class.getName());
So my question is this: Should I be worried? Is this just something that is hanging around for a bit but will eventually be GCd? I would think that if there was a leak it would grow higher than 2.
I'm closing this question. Someone at google has responded with the following:
OK, in that case then it's not an AppCompat bug since the standard
Action Bar implementation is used on ICS+.
Looking at that MAT screenshot, the framework's ActionMenuItemView is
being referenced from a clipboard event which is being finalized,
hence about to be GC'd. The LayoutInflater is probably the
LayoutInflater that the Activity keeps itself (getLayoutInflater()).