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;
}
}
Related
I want to manipulate a library I just discovered to drag/rotate and pinch zoom images. I am using two sources:
-The library itself: MultiTouchController.java https://code.google.com/p/android-multitouch-controller/source/browse/MTController/src/org/metalev/multitouch/controller/MultiTouchController.java
-An implementation of this library: PhotoSortrView https://code.google.com/p/android-multitouch-controller/source/browse/MTPhotoSortr/src/org/metalev/multitouch/photosortr/PhotoSortrView.java
-an example of Activity which implements this source:
public class PhotoSortrActivity extends Activity {
PhotoSortrView photoSorter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setTitle(R.string.instructions);
photoSorter = new PhotoSortrView(this);
setContentView(photoSorter);
}
#Override
protected void onResume() {
super.onResume();
photoSorter.loadImages(this);
}
#Override
protected void onPause() {
super.onPause();
photoSorter.unloadImages();
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
photoSorter.trackballClicked();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
The problem is the activity doesn't remember the position of the images when it leaves the foreground and I recall it after. Worse, the images are set randomly each time the Activity is called.
How can I make the Activity remember the state of the images? (the specific issue in my case is that it is a custom view)
Here is an example of what I want the Activity to remember, and then the reset made by the Activity after being called again:
See the savedInstanceState parameter in onCreate()? You override onSaveInstanceState() to set up a bundle that remembers your Activity state, and it shows up in the onCreate().
Now, if your question is: How do you save Activity state without changing the PhotoSortrView class; well, you can't. You would have to add some state parameter to loadImages() so that it loads the images and sets the current image, and maybe change unloadImages() so that it returns some state information that the Activity can save.
My app works with a long form which I decided to divide in multiple Fragments in a ViewPager. When you press the "save" option, the validation process starts.
Basically the validation is that some EditTexts are not empty. I'm looping through all Fragments in the ViewPager check if all fields has valid values.
// Inside Fragment
public boolean areFieldsValid() {
return !mEditText.getText().toString().isEmpty()
}
public void showErrors() {
mEditText.setError("cannot be blank");
}
If a field inside a Fragment is not valid, then viewPager.setCurrentItem(position, true); and fragment.showErrors() are called to go to that Fragment and show the user the error.
The problem comes when onCreateView() hasn't been called on the Fragment that has the error.
This happens either because you haven't navigated to that Fragment yet (supposing the user's on fragment1, error is on fragment7 and the user pressed "save" while on fragment1) or because the user rotated the device and all views are destroyed on every Fragment.
This problem/issue is not only that mEditText would be null, but also that the Fragment saved its state, so it might not even been blank. In other words, the following code is not an option, because even if the pointer is null, it might not be empty.
// Inside Fragment
public boolean areFieldsValid() {
return mEditText != null && !mEditText.getText().toString()isEmpty();
}
At this point I'm wondering if my architecture is wrong. I decided to go with ViewPager cause the form is really long, and I've been passing data from Fragment to Activity through callbacks.
Given the above settings, how can I validate fields and show the user which field is the one with the error?
You can't just assume that UI components will be there anytime you want. That fragment might be gone, killed or worse, destroyed without saving it's instance state.
What I offer is to save data on database and check if everything is set on save button click event. This can be done using ContentProviders and SQLiteDatabase. As Virgil Said in here "Persist more, persist often."
I have implemented a similar thing, but my approach is to go fragment by fragment. Hope this helps.
I add an interface,
public interface AddActionInterface {
public void onAddButtonClicked();
}
I created a base fragment which implements this interface as,
public abstract class BaseFragment extends Fragment implements AddActionInterface {
#Override
public void onAddButtonClicked() {
if (isAdded() && isVisible()) {
executeAction();
}
}
protected abstract void executeAction();
}
Then we will call our Interface object like this in the activity. Create a List like below,
List<AddActionInterface> listeners = new ArrayList<AddActionInterface>();
and add your fragment to the list inside the view pager as,
listeners.add(fragment);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment, tag).commit();
Simply call the below in the onOptionsItemSelected method.
if (item.getItemId() == R.id.action_add) {
for (AddActionInterface listener : listeners) {
listener.onAddButtonClicked();
}
}
What the above method does is calls the onAddButtonClicked() method which is implemented in the BaseFragment.
Trick here is that every time the button in the action bar is clicked it will pass the control to the BaseFragment which checks if the current fragment is still attached then will call the executeAction() method of the respective fragment which being abstract every fragment can have their own implementation.
So say for FragmentA you will simply have to extend it from BaseFragment and override executeAction() method. You can write fragment specific implementations.
This process is called dependency inversion principle. See if you can put all these pieces in right place else let me know. :) Wow this is huge. :)
On the viewpager class:
public void validate() {
for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
Fragment fragment = mSectionsPagerAdapter.getItem(i);
if(!(fragment instanceof Validetable)) {
return;
}
Validetable validetable = (Validetable) mSectionsPagerAdapter.getItem(i);
Fragment invalidFragment = validetable.validate();
if (invalidFragment == null) {
Toast.makeText(getActivity(), "valido", Toast.LENGTH_LONG).show();
}
else {
mViewPager.setCurrentItem(i);
break;
}
}
On each fragment you do:
public static boolean isValid = true;
#Override
public Fragment validate() {
if ( StringUtils.isBlank(ColetaLocal.getInstance().getNivel())) {
isValid = false;
return this;
}
isValid = true;
return null;
}
#Override
public void onResume () {
super.onResume();
treatErrorsShowing();
}
private void treatErrorsShowing() {
if (!isValid) {
showErrors();
}
else {
clearErrors();
}
}
I ended up validating each Fragment before moving to the next one.
Reason:
The initial idea was to validate on save, and if there was an Fragment with invalid data, move to that fragment and show the errors. But since there is no way to determine the state of Views inside a Fragment if it is not visible, you cannot validate input.
I made a Fragment, containing a WebView and I wanted to define an onKeyDown() to get back from one web page to the previous one. I did it, but the weirdest part for me was to share a WebView variable from my Fragment class to my Activity class because I can't define onKeyDown() in the Fragment. So I just defined a get method and made it static. But I wonder if it was a real mistake and my app can seriously crash in some circumstances.
My Fragment code:
public class BrowserFragment extends Fragment {
private static WebView webView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState){
View v = inflater.inflate(R.layout.fragment_activity, parent, false);
getActivity().setTitle(R.string.title_rus);
webView = (WebView) v.findViewById(R.id.webView);
webView.setWebViewClient(new SwingBrowserClient());
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
Uri data = Uri.parse("http://www.swinginmoscow.ru/m/");
webView.loadUrl(data.toString());
return v;
}
public static WebView getWebView() {
return webView;
}
}
And my Activity code:
public class MainBrowserActivity extends SingleFragmentActivity {
#Override
protected Fragment createFragment() {
return new BrowserFragment();
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && BrowserFragment.getWebView().canGoBack()) {
BrowserFragment.getWebView().goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
It might work, but it is not a good idea. It could very well cause a crash if you don't handle the Fragment correctly or are somewhere in your code a little careless regarding its lifecycle. But there is an easy way around this. Instead of using a static method, save the instance and call methods on the instance itself. This way you can check if the instance is null and if not the Fragment can handle calls to goBack() or canGoBack() itself:
public class MainBrowserActivity extends SingleFragmentActivity {
BrowserFragment browserFragment = null;
#Override
protected Fragment createFragment() {
this.browserFragment = BrowserFragment.newInstance();
return this.browserFragment;
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && this.browserFragment != null && this.browserFragment.canGoBack()) {
this.browserFragment.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
As you can see the BrowserFragment instance is saved and then methods like goBack() or canGoBack() are called on the BrowserFragment itself. Of course you have to implement these methods in the BrowserFragment but that should not be a problem:
public class BrowserFragment extends Fragment {
public static BrowserFragment newInstance() {
BrowserFragment fragment = new BrowserFragment();
return fragment;
}
private WebView webView;
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState){
View v = inflater.inflate(R.layout.fragment_activity, parent, false);
getActivity().setTitle(R.string.title_rus);
webView = (WebView) v.findViewById(R.id.webView);
webView.setWebViewClient(new SwingBrowserClient());
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
Uri data = Uri.parse("http://www.swinginmoscow.ru/m/");
webView.loadUrl(data.toString());
return v;
}
public boolean canGoBack() {
return this.webView != null && this.webView.canGoBack();
}
public void goBack() {
if(this.webView != null) {
this.webView.goBack();
}
}
}
I made a few extra improvements to your code. First of all I added null checks to prevent any possible NullPointerExceptions and secondly it is recommend to always use a static factory method to create new instances of Fragments. That is what the static newInstance() method is that I added to the BrowserFragment. The advantage of that is that you can implement a method which takes care of setting up the BrowserFragment for you regardless of where you use it. You can add parameters to newInstance() method to pass some value to the BrowserFragment or to add some required listener etc but since you don't pass any values to the BrowserFragment the newInstance() method remains pretty empty. Nevertheless it is best practice to always use such factory methods even if they only call new BrowserFragment().
Generally this approach is much better. Especially from a architecture perspective because you don't directly interact with the WebView in the Activity. The WebView doesn't have anything to do with the Activity, it is part of the implementation of the BrowserFragment and as such the Activity should not know that there even is a WebView. How calls to goBack() or canGoBack() are implemented or what they exactly do is of no interest to the Activity. The Activity just tells the BrowserFragment "I want to go back" and the BrowserFragment does the work. This separates the responsibilities better and makes the code more readable, more clear and more maintainable.
EDIT:
Also I don't know of a SingleFragmentActivity but generally any Activity implements onBackPressed() method. You don't have to override onKeyDown() to catch a back key event. You can just do something like this:
#Override
public void onBackPressed() {
if (this.browserFragment != null && this.browserFragment.canGoBack()) {
this.browserFragment.goBack();
} else {
// The back key event only counts if we execute super.onBackPressed();
super.onBackPressed();
}
}
If you have any other questions please feel free to ask!
How to define a Backpress Action in
class thats extends Fragment implements ActionBar.TabListener, how to define a backpressed action?
Fragments don't have an onBackPressed() callback like Activities do. You can try making your Activity maintain (or obtain) a reference to the fragment and have it call the fragment from within onBackPressed().
Fragment code:
public boolean onBackPressed() {
// your special behavior here
// return true if you have consumed the back press
}
Activity code:
public void onBackPressed() {
MyFragment fragment = getFragmentManager().findFragmentById(/* some unique id*/);
// could alternatively use findFragmentByTag() using a unique String
if (fragment != null) {
if (fragment.onBackPressed()) return;
}
// back press not consumed; allow usual behavior
super.onBackPressed();
}
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.