Ok, so the thing is that i'm trying to get my android application to have a settings activity for the user to be able to change some of the application's features (language, theme, ...). My problem comes when trying to get the app to react to the change on the value of one of those preferences. For instance the theme one; my idea would be to have a "Switch preference". When it would be on, the app's theme would be Material.Light and when off, Material. For this I though to have some "onValueChanged" method that would react when the switch changed its position. The problem here is that I'm unable to properly get an instance of the SwitchPreference in my SettingsActivity, both because the "findPreference(key)" method is deprecated and I don't really know how to make it take the value of the needed key.
There is any way to do this, or should I change the way of thinking for this problem?
Instead of setting the theme in onValue change listener, you can also do like below:
1) Create ThemeUtils class and do switch case for theme selection and set a theme for activity. I created own style, you can select your stlye name here
public class ThemeUtils {
public final static int THEME_Professional = 1;
public final static int THEME_Default = 2;
public static void onActivityCreateSetTheme(Activity activity, int sTheme) {
switch (sTheme)
{
default:
case THEME_Professional:
activity.setTheme(R.style.ProfessionalTheme);
break;
case THEME_Default:
activity.setTheme(R.style.DefaultTheme);
break;
}
}
}
2)In onCreate method of Activity, call setTheme method
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme();
}
3)Gets user selected theme
private void setTheme() {
int themeInt = getThemeValue();
ThemeUtils.onActivityCreateSetTheme(this, themeInt);
}
4) After getting the style id, you can call ThemeUtils class method onActivityCreateSetTheme
private int getThemeValue() {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
Boolean isGrid = pref.getBoolean(getString(R.string.grid_switch), false);
if (isGrid) {
return 2;
} else {
return 1;
}
}
Hope this option helps you !!!
When I open my app, an Activity is started, and inside its onCreate method I'm checking some conditions. If the condition is true, I finish my current Activity and open another one. The problem is: The first activity blinks on the screen and then the second one is opened. The code is below:
public class FirstActivity extends Activity {
#Override
protected final void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//some code here...
checkSomeStuff();
setContentView(R.layout.my_layout);
//some code here...
}
private void checkSomeStuff() {
if (/*some condition*/) {
final Intent intent = new Intent(this, SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
finish();
startActivity(intent);
}
}
}
Notice that setContentView() is after the check, but before the second activity is started, the first one still blinks on the screen.
Does anyone know how to make it not blink?
Thanks in advance.
The purpose of finish() is to destroy the current activity and remove it from the back stack. By calling finish then firing the intent, you are asking the activity to destroy it self (I assume the blink is it trying to recover) then firing the intent to the second. Move finish to after startActivity()
#Override
protected final void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//some code here...
if(checkSomeStuff()) {
setContentView(R.layout.my_layout);
//some code here...
}
}
private boolean checkSomeStuff() {
if (/*some condition*/) {
final Intent intent = new Intent(this, SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
return false;
}
return true;
}
The order of your code is wrong. You should not call anything after
finish();
This is because the activity will be destroyed. Anything following could result in strange behavior.
#Override
protected final void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//set my layout
setContentView(R.layout.my_layout);
//some code here...
finish();
//nothing here because activity will be destroyed
}
Flicker and blinking Activity
- Reason finish();
- Remove finish() in Activity
- added android:noHistory="true" in AndroidManifest
The android:noHistory will clear Activity from stack which need to be clear on back press otherwise it shows activity A on screen.
The below-mentioned trick is working perfectly for me. Hope this could useful for others.
Opening activity B from activity A.
To close activity B I am using:
finish();
overridePendingTransition(0, 0);
to avoid the black color blinking problem.
instead of doing
checkSomeStuff();
setContentView(R.layout.my_layout);
you should do
private void checkSomeStuff() {
if (//some condition) {
final Intent intent = new Intent(this, SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
finish();
startActivity(intent);
}else{
setContentView(R.layout.my_layout);
}
}
you see the view because the intent does not fire until onCreate is finished so setContentView gets called
Perhaps you can separate your condition better to entirely avoid setContentView() when you're planning to finish().
public class FirstActivity extends Activity {
#Override
protected final void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (someCondition()) {
goToNextActivity();
} else {
setContentView(R.layout.my_layout);
//some code here...
}
}
private boolean someCondition() {
/* return result of some condition */
}
private void goToNextActivity() {
final Intent intent = new Intent(this, SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
finish();
startActivity(intent);
}
}
Try this with your style
<style name="CHTheme" parent="Theme.AppCompat.Light">
<item name="android:windowDisablePreview">true</item>
</style>
From what I have researched, Android does not have a practical way to change overall application themes.
My application is somewhat simple, Activity wise, and I think this method of handling theme changes is safe.
I want to know if the method below is a safe, or is this a hack job and there are better ways to implement application wide themes?
Notes:
MainActivtiy.java is the entry point and only Activity besides the SettingsActivty.java
SettingsActivity.java extends the PreferenceActivty to display a typical preference screen. The theme setting is stored in the default shared preference identified by R.string.colorThemeListPrefStr where the android:entryValues are {"0", "1"}
Settings.java is just a class for static variables that are safe while the application is in memory, anything that needs to be saved between session is saved to shared preferences during onPause().
MainActivity.java:
public class MainActivity extends ListActivity implements OnClickListener{
public void onCreate(Bundle savedInstanceState) {
sp = PreferenceManager.getDefaultSharedPreferences(this);
// get the int representing the theme selected from shared preferences
switch (Integer.valueOf(sp.getString(getString(R.string.colorThemeListPrefStr), Settings.DEFAULT_COLOR_THEME_INDEX))) {
case 0:
super.setTheme(android.R.style.Theme_Light);
break;
case 1:
super.setTheme(android.R.style.Theme_Black);
break;
}
super.onCreate(savedInstanceState);
// ...
}
public void onPause() {
super.onPause();
// ...
// store the current theme int
Settings.currentTheme = Integer.valueOf(sp.getString(getString(R.string.colorThemeListPrefStr), Settings.DEFAULT_COLOR_THEME_INDEX));
// ...
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.inputSettingsButton:
startActivityForResult(new Intent(this, SettingsActivity.class), Settings.PREFERENCES_REQUEST_CODE);
break;
// ...
}
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case Settings.PREFERENCES_REQUEST_CODE:
// check if the new and old themes are different
if (Settings.currentTheme != Integer.valueOf(sp.getString(getString(R.string.colorThemeListPrefStr), Settings.DEFAULT_COLOR_THEME_INDEX))) {
this.finish();
startActivity(new Intent(this, MainActivity.class));
}
break;
// ...
}
}
}
You should restart your Application using this simple one line of code that comes with the support library.
startActivity(IntentCompat.makeRestartActivityTask(getActivity().getComponentName()));
After I do some change in my database, that involves significant change in my views, I would like to redraw, re-execute onCreate.
How is that possible?
UPDATE: Android SDK 11 added a recreate() method to activities.
I've done that by simply reusing the intent that started the activity. Define an intent starterIntent in your class and assign it in onCreate() using starterIntent = getIntent();. Then when you want to restart the activity, call finish(); startActivity(starterIntent);
It isn't a very elegant solution, but it's a simple way to restart your activity and force it to reload everything.
Call the recreate method of the activity.
Option 1
Call recreate() on your Activity.
However this method causes a flashing black screen to appear during the activity re-creation.
Option 2
finish();
startActivity(getIntent());
No "flashing" black screen here, but you'll see a transition between the old and the new instances with a not-so-pleasant black background. We can do better.
Option 3
To fix this, we can add a call to overridePendingTransition() :
finish();
startActivity(getIntent());
overridePendingTransition(0, 0);
Good bye black screen, but in my case I still see some kind of transition (a fade animation), on a colored background this time. That's because you're finishing the current instance of your activity before the new one is created and becomes fully visible, and the in-between color is the value of the windowBackground theme attribute.
Option 4
startActivity(getIntent());
finish();
Calling finish() after startActivity() will use the default transition between activities, often with a little slide-in animation. But the transition is still visible.
Option 5
startActivity(getIntent());
finish();
overridePendingTransition(0, 0);
To me, this is the best solution because it restarts the activity without any visible transition, like if nothing happened.
It could be useful if, for example, in your app you expose a way to change the display language independently of the system's language. In this case, whenever the user changes your app's language you'll probably want to restart your activity without transition, making the language switch look instantaneous.
Combining some answers here you can use something like the following.
class BaseActivity extends SherlockFragmentActivity
{
// Backwards compatible recreate().
#Override
public void recreate()
{
if (android.os.Build.VERSION.SDK_INT >= 11)
{
super.recreate();
}
else
{
startActivity(getIntent());
finish();
}
}
}
Testing
I tested it a bit, and there are some problems:
If the activity is the lowest one on the stack, calling startActivity(...); finish(); just exist the app and doesn't restart the activity.
super.recreate() doesn't actually act the same way as totally recreating the activity. It is equivalent to rotating the device so if you have any Fragments with setRetainInstance(true) they won't be recreated; merely paused and resumed.
So currently I don't believe there is an acceptable solution.
When I need to restart an activity, I use following code. Though it is not recommended.
Intent intent = getIntent();
finish();
startActivity(intent);
for API before 11 you cannot use recreate(). I solved in this way:
Bundle temp_bundle = new Bundle();
onSaveInstanceState(temp_bundle);
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("bundle", temp_bundle);
startActivity(intent);
finish();
and in onCreate..
#Override
public void onCreate(Bundle savedInstanceState) {
if (getIntent().hasExtra("bundle") && savedInstanceState==null){
savedInstanceState = getIntent().getExtras().getBundle("bundle");
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//code
}
After looking for the gingerbread implement for recreate, I'd like to use following codes (for gingerbread):
activity.mMainThread.mAppThread.scheduleRelaunchActivity(activity.mToken, null, null, 0, false, null);
For these codes, it's from the implementation in higher api.
public void recreate() {
if (mParent != null) {
throw new IllegalStateException("Can only be called on top-level activity");
}
if (Looper.myLooper() != mMainThread.getLooper()) {
throw new IllegalStateException("Must be called from main thread");
}
mMainThread.requestRelaunchActivity(mToken, null, null, 0, false, null, false);
}
Api-10 has no requestRelaunchActivity, however, from the diff, i found this:
public final void scheduleRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<Intent> pendingNewIntents,
int configChanges, boolean notResumed, Configuration config) {
- ActivityClientRecord r = new ActivityClientRecord();
-
- r.token = token;
- r.pendingResults = pendingResults;
- r.pendingIntents = pendingNewIntents;
- r.startsNotResumed = notResumed;
- r.createdConfig = config;
-
- synchronized (mPackages) {
- mRelaunchingActivities.add(r);
- }
-
- queueOrSendMessage(H.RELAUNCH_ACTIVITY, r, configChanges);
+ requestRelaunchActivity(token, pendingResults, pendingNewIntents,
+ configChanges, notResumed, config, true);
}
So I think I could use scheduleRelaunchActivity instead of requestRelaunchActivity.
And I have written them using reflect:
package me.piebridge.util;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.res.Configuration;
import android.os.Build;
import android.os.IBinder;
public class GingerBreadUtil {
private static Field scanField(Class<?> clazz, String... names) {
for (String name : names) {
Field field;
try {
field = clazz.getDeclaredField(name);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {
}
try {
field = clazz.getField(name);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {
}
}
return null;
}
public static void recreate(Activity activity) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) {
recreateHC(activity);
} else {
try {
recreateGB(activity);
} catch (InvocationTargetException e) {
e.getTargetException().printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static void recreateHC(Activity activity) {
((Activity) activity).recreate();
}
private static void recreateGB(Activity activity) throws IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Field Activity$mToken = scanField(Activity.class, "mToken");
IBinder mToken = (IBinder) Activity$mToken.get(activity);
Field Activity$mMainThread = scanField(Activity.class, "mMainThread");
Object mMainThread = Activity$mMainThread.get(activity);
Field ActivityThread$mAppThread = scanField(mMainThread.getClass(), "mAppThread");
Object mAppThread = ActivityThread$mAppThread.get(mMainThread);
Method method = mAppThread.getClass().getMethod("scheduleRelaunchActivity",
IBinder.class, List.class, List.class, int.class, boolean.class, Configuration.class);
method.invoke(mAppThread, mToken, null, null, 0, false, null);
}
}
I'm using these codes for the back-porting of xposed framework.
Call the recreate() method from where you want to recreate your activity . This method will destroy current instance of Activity with onDestroy() and then recreate activity with onCreate().
If this is your problem, you should probably implement another way to do the view filling in your Activity. Instead of re running onCreate() you should make it so onCreate() calls your filling method with some argument. When the data changes, the filling method should get called with another argument.
The way I resolved it is by using Fragments. These are backwards compatible until API 4 by using the support library.
You make a "wrapper" layout with a FrameLayout in it.
Example:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Then you make a FragmentActivity in wich you can replace the FrameLayout any time you want.
Example:
public class SampleFragmentActivity extends FragmentActivity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.wrapper);
// Check that the activity is using the layout version with
// the fragment_container FrameLayout
if (findViewById(R.id.fragment_container) != null)
{
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null)
{
return;
}
updateLayout();
}
}
private void updateLayout()
{
Fragment fragment = new SampleFragment();
fragment.setArguments(getIntent().getExtras());
// replace original fragment by new fragment
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment).commit();
}
In the Fragment you inflate/replace you can use the onStart and onCreateView like you normaly would use the onCreate of an activity.
Example:
public class SampleFragment extends Fragment
{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.yourActualLayout, container, false);
}
#Override
public void onStart()
{
// do something with the components, or not!
TextView text = (TextView) getActivity().findViewById(R.id.text1);
super.onStart();
}
}
Also depending on your situation, you may need getActivity().recreate(); instead of just recreate().
For example, you should use it if you are doing recreate() in the class which has been created inside class of activity.
In case you want to use recreate and target Android versions lower than 11, use ActivityCompat.recreate(...) using the platform support APIs.
I once made a test app that uploads, deletes, and then redownloads the database file using firebase cloud storage.
To display the data in database, the following code was the only solution I found. Neither recreate() nor finish() worked in this case.
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
startActivity(intent);
System.exit(0);
If you want to pass a parameter to onCreate() then you have to create a new intent with adding extra and call StartActivity with it. Here is a simple example which i did using this way.
String eczSabit = sa.getItem(position).getValue();
if(!Util.IsNullOrEmpty(eczSabit)){
sabit = Long.parseLong(eczSabit);
Intent intent = new Intent(eczaneSegmentasyon.this,eczaneSegmentasyon.class);
intent.putExtra("sabit", sabit);
startActivity(intent);
}
i found out the best way to refresh your Fragment when data change
if you have a button "search", you have to initialize your ARRAY list inside the button
mSearchBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mList = new ArrayList<Node>();
firebaseSearchQuery.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()) {
Node p = dataSnapshot1.getValue(Node .class);
mList.add(p);
}
YourAdapter = new NodeAdapter(getActivity(), mList);
mRecyclerView.setAdapter(YourAdapter );
}
If you're just looking to re-do your view, I had the exact same issue. In the onResume function try putting this:
mView = new AndroidPinballView(getApplication());
This was also in my onCreate(), so putting this in the onResume worked for me :)