Update an activity after use of optionsMenu - android

I have an app with 5 activities. All these activities have settings that can be modified using the menu-button (optionsmenu) and selecting 'Settings'. This will open a dialog where all settings shown and where modification is possible.
When I close this settings-dialog by press the 'ok'-button, I want the activity that called optionsmenu to update its view.
The optionsmenu is activited like this in all activities:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.firstactivity_options_menu, menu);
return true;
}
And an example of onOptionsItemSelected follows...
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.settings) {
class.settingsDialog(this);
} else if (item.getItemId() == R.id.about) {
try {
alertDialog(getResources().getString(R.string.settings_about), getAssets().open("about"), this);
} catch (IOException e) {
}
}
return true;
}
My problem is updating an activities view after I've had a match for R.id.settings. Is this possible? And if so, please give me some pointers...

There's probably different ways to do this but after clicking Ok you can use an Intent to start the activity and call finish() on the activity to re-create the views

I would implement a super/subclass approach or an interface since Dialogs do not force an Activity's onResume() method to be called.
Make a top abstract Activity class, e.g. SuperMainActivity.
public abstract class SuperMainActivity extends Activity
{
public abstract void updateUI();
}
Then, have each of your Activities extend SuperMainActivity instead of just Activity and implement the updateUI() method.
Then in your settingsDialog() method, make it either:
Accept a SuperMainActivity param instead of an Activity/Context param.
or:
Do a cast when you want to callback, such as
((SuperMainActivity)myVariable).updateUI();
An interface is largely similar:
public interface ActivityCallback
{
public void updateUI();
}
And each activity will implement ActivityCallback
such as
public class MainActivity implements ActivityCallback
{
public void updateUI()
{
//implementation. Differs per class
}
}
Then again, your settingsDialog() method should accept in an ActivityCallback parameter or you will cast again.
Note that if you do decide on this approach, when you call settingsDialog() you can still call it with
settingsDialog(this);
Since your Activities will meet the requirements for a parameter.

Related

How to use onBackPress in Fragment

In my application I use ViewPager for show two fragments in an activity.
In one of the fragments I use NavigationDrawer. I want when click on onBackPress close this NavigationDrawer.
I wrote below code for open this Drawer :
reviewSerialFrag_DrawerLayout.openDrawer(Gravity.END);
I want that when I click on onBackPress it will close this drawer with below code:
reviewSerialFrag_DrawerLayout.closeDrawer(Gravity.END);
questioner, put in what the current problem is, please
onBackpress() only called in fragment if you need back press event in fragment you have to implement interface to get onBackPress() callback.
In Activity:
public MyActivity extends AppCompatActivity{
private BackPressListener backPressListener;
#Override
public void onBackPressed() {
if (backPressListener != null) {
backPressListener.onActivityBackPress();
} else {
super.onBackPressed();
}
}
public void setBackPressListener(BackPressListener backPressListener) {
this.backPressListener = backPressListener;
}
public interface BackPressListener{
void onActivityBackPress();
}
}
In Fragment:
public class MyFragment extends Fragment implements BackPressListener{
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyActivity)getActivity()).setBackPressListener(this);
}
#Override
public void onActivityBackPress() {
// handle your back press here.
reviewSerialFrag_DrawerLayout.closeDrawer(Gravity.END);
getActivity().onBackPressed();
}
#Override
public void onDestroy() {
super.onDestroy();
((MyActivity)getActivity()).setBackPressListener(null);
}
}
My solution:
Create BaseFragment.java:
public class BaseFragment extends Fragment
{
public boolean onBackPressed()
{
return false;
}
}
then extend your fragment by this BaseFragment, and in activity:
#Override
public void onBackPressed()
{
if (!yourFragment.onBackPressed())
super.onBackPressed();
}
In yourFragment:
public class YourFragment extends BaseFragmnet
{
...
.
.
#Override
public boolean onBackPressed()
{
// do something...
return true; // you should return true;
}
}
According to the AndroidX release notes, androidx.activity 1.0.0-alpha01 is released and introduces ComponentActivity, a new base class of the existing FragmentActivity and AppCompatActivity. And this release brings us a new feature:
You can now register an OnBackPressedCallback via addOnBackPressedCallback to receive onBackPressed() callbacks without needing to override the method in your activity.
The best solution is create your interface, and implement in Fragment.
Solution here : implement onBackpress in Fragment
public interface IOnBackPressed {
/**
* Si vous retouné true le back press ne sera pas pris en compte, sinon l'activité agira naturellement
* #return true si votre traitement est prioritaire sinon false
*/
boolean onBackPressed();
}
see link for more details ... easy sample
You can keep the flag that drawer was opened. And when you override the onBackPressed(), check this flag and if it's true, call
reviewSerialFrag_DrawerLayout.closeDrawer(Gravity.END);
otherwise, call super.onBackPressed() or any other logic you need.

Generic handle OnBackPressed()

I have a BaseActivity() that have many activities and a BaseFragment() that have many fragments. Each activity contains 2-3 fragments and I need to make a generic method to handle each onBackPressed from all fragments (all - means all app screens) but this method should be in Base Fragment() (every fragment extends it). I supose that I'll need a kind of listener to tie OnBackPressed() from BaseActivity() to genericMethod() from BaseFragment()
Thanks in advice.
#Choletski:
onBackPressed()
It will be called when the activity has detected the user's press of the back key. The default implementation simply finishes the current activity, but you can override this to do whatever you want.while overriding the default back button action as it is not suggested to change the android default user experience.
Override the onBackPressed() method and take the action inside this function.
#Override
public void onBackPressed() {
// Write your code here
super.onBackPressed();
}
How to implement onBackPressed() in Android Fragments?
The simplest solution rest to be a bit "hard programmed" in my case, like I mentioned in my question I need a method from BaseFragment() to handle all back press actions from all screens that means all fragments that extends this BaseFragment().
#Sharp Edge solution may be accepted but why to handle it in each SimpleActivity() that extends BaseActivity() if I can just add a single method in BaseFragment() and all simple activities that extends BaseActivity() will don't care about that.
#escape-llc solution is confused and not the expected one... I can handle it easier using EventBus or Otto and send from onResume() from each fragment to SimpleActivity(). So I'll receive the actual open fragment and I'll now what action to do when onBackPressed() is executed...
So, like I said, my solution is to use just a simple generic method in BaseFragment():
public void doBackBtnPressedAction(View view) {
view.setFocusableInTouchMode(true);
view.requestFocus();
view.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
//logical part, in my case some server requests
return true;
}
}
return false;
}
});
}
This is how i handled it when i had a webview in fragment and wanted to handle onBackPressed for the webview?
public class Tab2 extends Fragment {
ProgressBar progress;
WebView x;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v =inflater.inflate(R.layout.tab_2,container,false);
x = (WebView)v.findViewById(R.id.webView);
x.setOnKeyListener(new OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if(event.getAction() == KeyEvent.ACTION_DOWN)
{
WebView web = (WebView)v;
switch (keyCode)
{
case KeyEvent.KEYCODE_BACK:
if(web.canGoBack())
{
web.goBack();
return true;
}
break;
}
}
return false;
}
});
You have to make a custom Activity class for this.. and override its on onBackPressed() and add your logic in their. Then make sure wherever Fragments are used, you have to make the associated Activity sub class of this CustomActivity..
So whenever no matter on which Fragment user is, onBackPressed() of that Activity will be called and add super() to it.. so that it will call the base class's method and your code will run on each fragment.
example:
MyCustomActvity extends FragmentActivity{
#Override
public void onBackPressed(){
// your logic here
super.onBackPressed();
}
}
Now You know that Fragments must have at least 1 Base Activity, so just override that Activity's onBackPressed()
MyActivity extends MyCustomActivity{
// 3 fragments are called/replaced from this activity
// other code
#Override
public void onBackPressed(){
super.onBackPressed(); // it will invoke base class method and your code
}
}
Just extend MyCustomActivity for the ones which use Fragments.
Here is a great way to handle it in a general fashion. We use it now in all of our fragment-based apps.
First create an interface for fragments to implement. This represents whether they want to handle the back key at all. If not, don't implement the interface.
public interface IHandleBackPressed {
boolean handleBackPressed(Activity ax);
}
This is essentially a proxy for the activity's onBackPressed method.
Next, override the Activity.onBackPressed method with this boilerplate:
#Override
public void onBackPressed() {
final Fragment fx = getFragmentManager().findFragmentById(R.id.content);
if(fx != null) {
if(fx instanceof IHandleBackPressed) {
final IHandleBackPressed ihbp = (IHandleBackPressed)fx;
if(ihbp.handleBackPressed(this)) {
// we handled it
return;
}
}
}
// onBackPressed unhandled by us
super.onBackPressed();
}
This can be the same always. If you have multiple fragment areas, simply repeat the sequence for each one. If you have additional logic, integrate it before or after, but before you call super.onBackPressed to let the system take over (i.e. exit your activity).
Here is a sample of what a Fragment can do. This example uses a WebView and it wants to "use" the back key to manage the Back Stack of the WebView:
public class BrowseUrlFragment extends Fragment implements IHandleBackPressed {
WebView wv;
public boolean handleBackPressed(Activity ax) {
if(wv != null && wv.canGoBack()) {
wv.postDelayed(goback, 150);
return true;
}
return false;
}
}

Why does Activity's method onCreateOptionMenu() get called two times by using setHasOptionMenu(true) for it's Fragments?

I manage some Fragment's in my own ActionBarActivity named MainActivity. One Fragment is shown at one time. The example should be simple.
The Fragment which is showing should have got an option menu under certain conditions.
Here my code:
public class MainActivity extends ActionBarActivity{
...
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// The certain conditions. You might know the background. ;-)
final boolean hasMenu = mNavigationDrawer.isDrawerOpen() ? false : true;
// The reference to the fragment which is shown
mCurrentShownFragment.setHasOptionsMenu(hasMenu);
return super.onCreateOptionsMenu(menu);
}
...
}
Because of the invocation of mCurrentShownFragment.setHasOptionMenu(true) MainActivity's and Fragment's onCreateOptionMenu(...) is called two times.
First question: Why?
Second question: Is that fine?
Third question: If second question's answer is false. How could I prevent this?
Best regards,
Barock
Take a look at the source of setHasOptionsMenu :
public void setHasOptionsMenu(boolean hasMenu) {
if (mHasMenu != hasMenu) {
mHasMenu = hasMenu;
if (isAdded() && !isHidden()) {
mActivity.supportInvalidateOptionsMenu();
}
}
}
it calls the supportInvalidateOptionsMenu() :
public void supportInvalidateOptionsMenu() {
if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
// If we are running on HC or greater, we can use the framework
// API to invalidate the options menu.
ActivityCompatHoneycomb.invalidateOptionsMenu(this);
return;
}
mOptionsMenuInvalidated = true;
}
Which calls invalidateOptionsMenu(this) :
public void invalidateOptionsMenu ()
Added in API level 11
Declare that the options menu has changed, so should be recreated. The onCreateOptionsMenu(Menu) method will be called the next time it needs to be displayed.
So it's absolutly normal that it calls the onCreateOptionsMenu cause that's how setHasOptionsMenu works

How to call (and use) onOptionsItemSelected from MainActivity in other classes

I'm making my very first Android application but I ran into a problem.
I have over 8 different classes which all use the same actionbar.
Now in place of calling the method in every different class (and having a lot of double code) I would like to call the method of the main class in my other classes.
This is a part of my code for the onOptionsItemSelected in main.java
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.actionbar_new_income:
Intent newIncome = new Intent(this, NewIncome.class);
this.startActivity(newIncome);
return true;
}
}
Now I was wondering how I could call the method in another class (newIncome.java)
I have this so far, but it keeps saying I need to add arguments. And I ofcourse need to be able to detect which menuitem is clicked..
MainActivity main = new MainActivity();
main.onOptionsItemSelected();
Any help please?
Thanks!
You should not do this. If you have common code then put it in a class (not an activity) that is accessible by any activity that needs it.
You will still have some duplication but this is normal.
A good way of reducing activity launch code is to add a static method to each activity that you can call which launches the activity it is in.
E.g in your NewIncome Activity you could have
Public static void Launch(Context c) {
Intent newIncome = new Intent(c, NewIncome.class);
C.startActivity(newIncome);
}
You could then launch this activity from any other activity just by calling
NewIncome.Launch(this);
If required you can add parameters to the method and then add Extras to the Activity using these parameters.
You can do it like the following example if your menu entries are totally independent of the activity in which they are contained:
In each activity
#Override
public boolean onOptionsItemSelected(MenuItem item) {
return CommonClass.HandleMenu(this, item.getItemId());
}
In a common class
public class CommonClass {
public boolean HandleMenu (Context c, int MenuEntry) {
switch (MenuEntry) {
case R.id.actionbar_new_income:
NewIncome.Launch(c);
etc....
...
}
}
If your 8 classes are activities you may define a base activity with the onOptionsItemSelected which is the one where you put the elements in the actionbar you want. Then make the other activities derive from it.

How to handle AsyncTask onPostExecute when paused to avoid IllegalStateException

I appreciate the numerous postings regarding AsyncTask on a rotation change. I have the following problem when using the compatability lib and trying to dismiss a DialogFragment in onPostExecute.
I have a fragment which fires of an AsyncTask which displays a progress DialogFragment, then in onPostExecute dismisses the dialog and then potentially throws up another DialogFragment.
If when the progress dialog is being displayed I put the application into the background I get the following for my fragment:
1) onPause
2) onSaveInstanceState
3) onPostExecute in which I try to dismiss and invoke a dialog.
I get an IllegalStateException because I'm trying to effectively commit a transaction when the activity has saved its state and I understand this.
On a rotation I've assumed (perhaps incorrectly) that I wouldn't get an onPostExecute until the activity has been recreated. However, when putting the application into the background I assumed (definitely incorrectly) that the onPostExectute wouldn't get called while the fragment/activity was paused.
My question is, is my solution to simply detect in onPostExecute that the fragment/activity is paused and simply perform what I need to do in onResume instead? Seems somewhat ugly to me.
Thanks in advance, peter.
Edit 1
Need to support 2.1 and above
Edit 2
I have considered showing the dialog using FragmentTransaction:add and FragmentTransaction:commitAllowingStateLosshowever this isn't without its problems.
If you need to synchronize your task with the activity lifecycle, I believe that Loaders are exactly what you need. More specifically, you should use AsyncTaskLoader to do the job. So now instead of running an AsyncTask, you launch your loader, then wait for response in a listener. If the activity is paused, you won't get a callback, this part will be managed for you.
There is another way to handle this task: using a fragment which retains its instance. The general idea is that you create a fragment without UI and call setRetainInstance(true). It has a task which is being notified about the activity being available or not. If not, the task's thread suspends until an activity becomes available.
Another way of achieving what you require is to implement the PauseHandler class that I documented in this post.
Then in your onPostExecute method call sendMessage() to post your message into the handler.
When your application resumes the action will be handled.
Rather then using BroadcastReceiver, I prefer using bus libraries like guava, otto or eventbus. Their performance is much better then the broadcast receiver implementation.
I came up with a solution for this problem without any major workaround:
The basic idea how to maintain a progressdialog and a asynctask is described in this blogentry (of course I used the AsyncTaskComplex-Version). All credits go to the author of this blogentry, I only added a tiny thing:
Obviously I'm not using showDialog() anymore. Instead I stick with DialogFragments.
The second tweak is the importent one and also solves the problem with the IllegalStateException:
Instead of only telling the asynctask in onRetainCustomNonConfigurationInstance() that there is no more activity I also do it in onPause(). And instead of only telling the asynctask in onCreate() that there is a new activity I also do it in onResume().
And there you go, your AsyncTask will not try to inform your activity about his finish causing an IllegalStateException when the activity is not visible.
If you would like to see more code instead of words, leave a comment.
/edit:
Sourcecode to show my solution, which I think is a pretty decent one :)
public class MyActivity extends Activity {
private MyTask mTask;
#Override
protected void onCreate(Bundle pSavedInstanceState) {
super.onCreate(pSavedInstanceState);
setContentView(R.layout.editaccount);
Object retained = getLastCustomNonConfigurationInstance();
if ( retained instanceof NewContactFolderIdTask ) {
mTask = (MyTask) retained;
mTask.setActivity(this);
}
}
#Override
protected void onPause() {
if(mTask != null) {
mTask.setActivity(null);
}
super.onPause();
}
#Override
public Object onRetainCustomNonConfigurationInstance() {
if(mTask != null) {
mTask.setActivity(null);
return mTask;
}
return null;
}
#Override
protected void onResume() {
if(mTask != null) {
mTask.setActivity(this);
}
loadValues(); // or refreshListView or whatever you need to do
super.onResume();
}
public void onTaskCompleted() {
loadValues(); // or refreshListView or whatever you need to do
DialogFragment dialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(PROGRESS_DIALOG_FRAGMENT);
if(dialogFragment != null) {
dialogFragment.dismiss();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.main, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// app icon in Action Bar clicked; go home
Intent intent = new Intent(this, OXClient.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
case R.id.menu_refresh:
mTask = new MyTask(this);
mTask.execute();
break;
}
return super.onOptionsItemSelected(item);
}
private class NewContactFolderIdTask extends AsyncTask<Boolean, Integer, Bundle> {
private MyActivity mActivity;
private boolean mCompleted;
private NewContactFolderIdTask(MyActivity pActivity) {
this.mActivity = pActivity;
}
public void setActivity(MyActivity pActivity) {
this.mActivity = pActivity;
if(mCompleted) {
notifiyActivityTaskCompleted();
}
}
private void notifiyActivityTaskCompleted() {
if(mActivity != null) {
mActivity.onTaskCompleted();
}
}
#Override
protected Bundle doInBackground(Boolean... pBoolean) {
// Do your stuff, return result
}
#Override
protected void onPreExecute() {
DialogFragment newFragment = ProgressDialogFragment.newInstance();
newFragment.show(getSupportFragmentManager(), PROGRESS_DIALOG_FRAGMENT);
}
#Override
protected void onPostExecute(Bundle pResult) {
mCompleted = true;
notifiyActivityTaskCompleted();
}
}
}
On How to handle Handler messages when activity/fragment is paused I offer another approach using a BroadcastReceiver.
I consider it cleaner more elegant and it offers the advantages that you can invoke code on your base fragment from everywhere within your app and by using sticky broadcasts your invocation can be "remembered" and executed after your fragment resumes.

Categories

Resources