How can I have an activity B, which pops up and partially obscures a "parent" activity A, continuously send update info to A?
The normal mechanism, of course, would be to send an Intent back to A. But this can only happen once, when calling finish().
I suppose another way would be to have a handler in A and let B post to the handler. Getting the handler from A to B could be done through a "global" Application member.
Is there a better way?
EDIT: Using DialogFragment appears to be a good solution. However there is a position issue with DialogFragment. Please see my new post: https://stackoverflow.com/questions/30471032/position-dialogfragmet-relative-to-view
As far as I know, an Activity always covers another Activity. At any point, Android could reclaim memory and destroy Activity A.
This means that you should manage your data differently. Either through your Application instance, if there is not much to share.
But you probably should consider another storage mechanism. What kind of data do you want to pass to your Activity A ? And what do you mean by "partially obscures"?
EDIT
I would suggest that your DialogFragment keeps a reference to your Activity. Take a look at the developer page. You can try to implement something like this:
In you Activity, when you would like to show your dialog:
void showDialog() {
DialogFragment newFragment = MyAlertDialogFragment.newInstance(
R.string.alert_dialog_two_buttons_title);
newFragment.setActivity(this);
newFragment.show(getFragmentManager(), "dialog");
}
In your DialogFragment class, simply implement a setter method:
public static class MyAlertDialogFragment extends DialogFragment {
Activity activity;
//rest of the code here
public void setActivity(Activity a){
this.activity = a;
}
private void notifyActivity(){
int level = aMethod();
activity.somethingHappened(level);
}
}
Now, everytime you would like to call a method of your Activity, use the reference you passed previously.
I would also make an Interface, and make your Activity implement it. Like this, you are not dependent on one specific Activity, but it could be any UI component. Hope it helps.
Related
I have an app that hold post information in an activity. in this activity related posts listed in bottom of post. User by clicking on related post can go to post activity and see that post info and related posts too.
As you can see in image, I have Activity A that holds post and it's related posts. When user Click on post I send user to Activity A with new post id and fill activity by new data.
But I think this is not Right way!
should I used Fragment instead of Activity?
Opening another Instance of an Activity on top of another is simplest way of navigating a content graph. User can simply press back, and go to previously opened content, until user reaches back to starting Activity, then the application closes. Though pretty straight forward, this particular approach has two issues:
It may happen that a lot of Instances of same activity are on the stack, utilising a large amount of device resources like memory.
You don't have a fine grained control over Activity Stack. You can only launch more activities, finish some, or have to resort to intent flags like FLAG_CLEAR_TOP etc.
There is another approach, that re-uses the same Activity instance, loads new content in it while also remembering the history of content that was loaded. Much like a web browser does with web page urls.
The Idea is to keep a Stack of content viewed so far. Loading new content pushes more data to stack, while going back pops the top content from stack, until it is empty. The Activity UI always displays the content from top of the stack.
Rough Example:
public class PostActivity extends AppCompatActivity {
// keep history of viewed posts, with current post at top
private final Stack<Post> navStack = new Stack<>();
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// get starting link from intent extras and call loadPost(link)
}
private void loadPost(String link){
// Load post data in background and then call onPostLoaded(post)
// this is also called when user clicks on a related post link
}
private void onPostLoaded(Post post){
// add new post to stack
navStack.push(post);
// refresh UI
updateDisplay();
}
private void updateDisplay(){
// take the top Post, without removing it from stack
final Post post = navStack.peek();
// Display this post data in UI
}
#Override
public void onBackPressed() {
// pop the top item
navStack.pop();
if(navStack.isEmpty()) {
// no more items in history, should finish
super.onBackPressed();
}else {
// refresh UI with the item that is now on top of stack
updateDisplay();
}
}
#Override
protected void onDestroy() {
super.onDestroy();
// cancel any background post load, release resources
}
}
I would choose:
activity/fragment depends on complexity with:
horizontal recyclerview with custom expanded card view
and inside this expanded card view second vertical recyclerview :)
Here's what you can try.
Create a PostActivity which is a shell for fragments. Inside this activity you can just replace fragments using FragmentTransaction.
Your PostActivity can now have a PostFragment which will hold post and related posts. Now on click of post you can replace PostFragment with PostDetailFragment with postID being sent to the new fragment as a bundle. The PostDetailFragment will now display details according to id.
Check here: http://www.vogella.com/tutorials/Android/article.html#components_fragments
By seeing the picture the way i would implement is i would have create an activity with a bottom listview for your items and on top there would be a framelayout for holding fragments . when user click on any list item i would load the respective fragment in the activity
It all depends on what you are trying to achieve. What would you expect to happen when the user touches the back button after going down a couple of levels? If you want to the application to exit, no matter how deep in the sequence they have gone, then the best solution in my opinion is to simply reload the same activity with the new data and invaliding the affected views. If you need the back button to take the user back to the previous data, then the next question would be if you are keeping track of the past data breadcrumb. If so, then just intercept the back button and load the previous data for as long as there is data in your stack, or exit if you get to the top. If you don't want to keep track of the previous data chain, then instead of loading one activity with the new data, you can start a new activity of the same class, but with the new data. Android with keep the track of activities and each back button touch would close the running activity and take the user to the previous activity. Choice of activity versus fragment is just yours. You can use fragments that hold the data that you want to change after each user touch, create new ones when needed, disconnect the previous ones, and connect the new ones. You will need to do some extra work to make sure the back button works correctly (depending on you want the back button to behave). Based on what I can tell, it is simpler to just have one activity and load new data when needed and keep a trail of data changes, if you need to be able to go back.
It can be achieved using activity alone. Though I preferred moving all related UI to fragment.
You can use Navigator class.
Here the steps:
1. Add Navigator Class
public class Navigator {
private static Navigator instance;
public synchronized static Navigator getInstance() {
if (instance == null) {
instance = new Navigator();
}
return instance;
}
public void navigateToActivityA(Context context) {
Intent activity= AActivity.getCallingIntent(context);
context.startActivity(activity);
}
}
2. Add the calling method to your Activity class.
public static Intent getCallingIntent(Context context) {
return new Intent(context, AActivity.class);
}
3. Call the activity with the following code in your caller activity.
Navigator.getInstance().navigateToActivityA(this);
I suggest that you read about AndroidCleanArchitecture
For this task...
0) Starting new activity
I read again about question, and understood that you need advice for starting activity. So, starting new activity it's Ok, your main problem will be with another things (see below).
But lets talk about starting another data. Using Fragment instead doesn't resolve your task, fragments helps with different screen work. Using for example just data refreshing as a variant. You may use just single activity and refresh only data, it will look much better, if you also add animation, but not better than starting activity.
Using Fragment helps your with different screen actions. And maybe, answering on your question - it will be most suitable solution. You just use single acitivity - PostActivity, and several fragments - FragmentMainPost, FragmentRelated - which will be replaced, each other, by selecting from related post.
1) Issues with returning back
Lets imagine, that users clicks to new one activity and we loaded new data. It's Ok, and when Users clicks over 100 activities and receiving a lot of information. It's Ok, too. But main question here it's returning back (also another about caching, but lets leave it, for now).
So everyone know, it's bad idea to save a lot of activities in stack. So for my every application, with similar behavior we override onBackPressed in this activity. But how, lets see the flow below:
//Activities most have some unique ID for saving, for ex, post number.
//Users clicks to 100 new activities, just start as new activity, and
//finish previous, via method, or setting parameter for activity in AndroidManifest
<activity
noHistory=true>
</activity>
....
//When activity loaded, save it's activity data, for ex, number of post
//in some special place, for example to our Application. So as a result
//we load new activity and save information about it to list
....
// User now want return back. We don't save all stack this activities,
// so all is Ok. When User pressing back, we start action for loading
//activities, saved on our list..
.....
onBackPressed () {
//Getting unique list
LinkedTreeSet<PostID> postList =
getMyApplication().getListOfHistory();
//Getting previous Post ID based on current
PostID previousPostID = postList.get(getCurrentPost());
//Start new activity with parameter (just for ex)
startActivity(new Intent().putExtra(previousPostID));
}
RESULT
I found this as the best solution for this tasks. Because in every time - we work only with single activity!
I have seen a few versions of this question before, but the reasons for this exception were different than my own it seems.
What I am trying to do:
-Main Activity class has a toolbar at the bottom, clicking the buttons will display a series of fragments, one after another.
- A class EditItemFragmentManager, which is instatiated on a button click, and has methods that display specific fragments based on the toolbar button clicked.
I would like to use this manager class I created because it cleans my code up significantly and will make adding more features later helpful.
Here is my EditItemFragmentManager class, I am not sure if extending Activity is a good idea or not, I think that it will put my MainActivity on pause
public class EditItemFragmentManager extends Activity{
//instance variables
public EditItemFragmentManager(){
// initialization of some variables
}
public void editItem(){
editItemSequence();
}
private void editItemSequence(){
EditNameFragment enf = new EditNameFragment();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(editNameFragment, EDIT_FRAG_TAG);
fragmentTransaction.addToBackStack(EDIT_FRAG_TAG);
fragmentTransaction.commit();
}
}
So it blows up when commit(); is called, giving me
java.lang.IllegalStateException: Activity has been destroyed
This is how I am trying to get this fragment from my MainActivity,
#Override
public void onClick(View view) {
EditIteFragmetManager manager = new EditIteFragmetManager();
manager.editItem();
}
I am still learning about the Acvtivity lifecycle in Android. I think my problem is something due to this class extending Activity, which puts my Main on pause, and the FragmentTransaction has nothing to commit to? If so, I need to get the existing instance of my main activity and call it on that? This is where I'm a bit lost, if anyone who understands the lifecycle of Activities/Fragments explain how I could go about implementing this while still having a helper class such as this?
If you're using the SupportFragmentManager, then you need to extend from FragmentActivity, and not just Activity. Also make sure that you imported the Fragment from the v4 support library, and not android.app.
Other than that, you seem to be instantiating a subclass of Activity with "new", which is terrible. Create activities only using Intents.
I solved this issue by moving my manager class to become a private inner class of my main, since they are so tightly coupled. No fragment issues now.
I'm wondering why are the Fragments communicating through the container Activity are called reusable.
From: http://developer.android.com/guide/components/fragments.html I know that:
You should design each fragment as a modular and reusable activity
component. That is, because each fragment defines its own layout and
its own behavior with its own lifecycle callbacks, you can include one
fragment in multiple activities, so you should design for reuse and
avoid directly manipulating one fragment from another fragment.
Let's take an example; I have a DateSetFragment which contains two buttons; first button fires TimePickerDialog (FragmentDialog) which allows user to pick an hour and the second one DatePickerDialog (FragmentDialog) which allows user to pick a day. At the end gathered data should be sent back to the DateSetFragment.
According the: http://developer.android.com/training/basics/fragments/communicating.html:
All Fragment-to-Fragment communication is done through the associated
Activity. Two Fragments should never communicate directly.
communication between fragments should be done via interfaces through the container activity. Given that I should send my collected data from both Fragment Dialogs to the container activity and then from the Activity back to the DateSetFragment. I don't see how this make my DateSetFragment reusable and modular in any way. Doing so I have to implement fragment interfaces and some crucial logic in my container Activity which makes it connected with it.
The question is; Is it wrong in this situation if Fragment Dialogs will communicate directly with the DateSetFragment ?
It is "modular" because those interfaces are well defined and explicitly implemented by the hosting activity.
Anywhere you drop that fragment in, if the activity implements the callback interface defined by the fragment events the activity can choose what to do depending on what environment the fragment is attached in.
Example,
DatePickerFragment extends Fragment {
public interface DatePickerFragmentEventListener {
public void onDateSelected(DateTime dt);
}
}
Activity1 extends Activity implements DatePickerFragmentEventListener {
DatePickerFragment mDatePickerFragment;
OtherFragment mFragment2;
#Override
public void onDateSelected(DateTime dt) {
mFragment2.setSomeViewsText(dt.toString());
}
}
Activity2 extends Activity implements DatePickerFragmentEventListener {
DatePickerFragment mDatePickerFragment;
#Override
public void onDateSelected(DateTime dt) {
SharedPrefClient c = SharedPrefClient.getInstance();
c.setExpirationDateTime(dt);
}
}
I have a dateTimePickerFragment (or whatever), in one activity I have 2 fragments, when you change the date on the picker I want to update the other fragment's text view to display that date. In another activity I might use that exact same callback to write the chosen date to SharedPreferences.
The point is that the Fragment is a contained piece of UI interaction, and certain events it will notify the enclosing Activity of what just happened to it, so that the activity dictates that the result of an action on the fragment does to either other fragment or the application itself. There is no reason to implement a custom fragment for each and every situation.
Imagine this situation:
Activity.
Activity contains layout with 2 fragments. FragmentA and FragmentB
FragmentA has a layout with a button that should print a toast "Hello".
FragmentB has a layout with a button that should print a toast "Bye".
Both buttons have a onClick attribute set. First one with "sayHello" and second one with "SayBye".
So which is the correct way of finally displaying the toast?
Case A:
public class ActivityCustom extends FragmentActivity{
[...]
public void sayHello(View v){
//SHOW TOAST HERE
}
public void sayBye(View v){
//SHOW TOAST HERE
}
}
Case B:
public class ActivityCustom extends FragmentActivity{
[...]
public void sayHello(View v){
((FragmentA)this.findFragmentById(R.id.fragmentA)).showToast();
}
public void sayBye(View v){
((FragmentB)this.findFragmentById(R.id.fragmentB)).showToast();
}
}
I'm a bit confused about that.
Because if we delegate all the work on the fragments, I guess Activity will be kinda clear of code. It will just have the code to "connect" the two fragments, right?
I think this is a matter of OOP principles specifically "encapsulation", i don't think activity should contain code that do not care about, hence the proper way taking on count your example would be having each toast message in the Fragment because is their behavior and in case of modifications you actually know where to go, unlike having everything in the activity becoming the single point for every single functionality losing maintainability and extensibility, since there will be a point where all your code will rely on your activity. By the way there's nothing wrong on having Activities with just a few code, is actually good to keep code out of activity if it doesn't have to do with the activity life cycle itself...
Regards!
I believe you should show the toasts in each fragment. In FragmentA's onCreateView method, you get the button, add the listener and show the toast in the onCLick() method there. Same on FragmentB.
No problem for the activity to have less code.
I'm switching one of my applications over to fragments (from just activities) and either I'm totally missing something or they have severely complicated the whole process of showing an AlertDialog.
First, here's what I'm trying to do: show one of several alert dialogs with a positive and negative button with callback methods associated to each bottom. Dialog needs to survive (ie: be recreated) a screen rotation.
Before: In the past, all you had to do is create an AlertDialog with the proper callback methods, etc. and show it and the system would take care of everything including screen rotation.
Now: If I create and show an AlertDialog from my fragment, it doesn't get recreated during a screen rotation and according to the LogCat leaks memory during the destruction. According to the new developer docs on fragments, I should be using a DialogFragment to create a AlertDialog so that the fragment manager can handle things like screen rotation, etc. (see here: http://developer.android.com/reference/android/app/DialogFragment.html under the Alert Dialog heading) That's fine, however, the problem is the callback methods. In the provided example, they are hard coded to two methods in the Activity. I have two problems with that, I don't want to have the activity involved in this process, and I need different callback methods for different AlertDialog I create. I really don't want to have to create different classes with hard coded callbacks for each AlertDialog I will be creating. There has to be a simpler way, otherwise this is just stupidity :)
Another way to put things ... fragments are recreated after a screen rotation by the fragment manager using any "arguments" that were saved during the creation process. These arguments are saved inside a Bundle, however, I can't save a callback method inside a Bundle, hence the fragment manager can't recreate a fragment with a passed callback method, only a hard coded one, which means I need separate classes with hard coded callback methods for each type of AlertDialog I will be displaying ... is this stupid or am I just missing something here?
Thanks for any help,
Harry
You can use interfaces to tidy up the hardcoded callbacks a bit.
In the following sample, my dialog fragment class specifies an interface called Host. Activities that want to use this fragment will have to implement MyAlertDialog.Host interface and have the two methods it defines. Of course, instead of generic onOptionOne and onOptionTwo names you could use onReport, onRetry --whatever makes sense for each alert.
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
public class MyAlertDialog extends DialogFragment {
/**
* Host activities have to implement this interface to receive button click
* callbacks.
*
*/
public static interface Host {
public void onOptionOne();
public void onOptionTwo();
}
public static MyAlertDialog newInstance() {
return new MyAlertDialog();
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Host host;
try {
host = (Host) getActivity();
} catch (ClassCastException e) {
String name = getActivity().getClass().getName();
throw new RuntimeException("Class " + name + " doesn't implement MyAlertDialog.Host interface");
}
if (which == DialogInterface.BUTTON_POSITIVE)
host.onOptionOne();
if (which == DialogInterface.BUTTON_NEGATIVE)
host.onOptionTwo();
}
};
return new AlertDialog.Builder(getActivity())
.setMessage("Message here")
.setPositiveButton("Option One", clickListener)
.setNegativeButton("Option Two", clickListener)
.create();
}
}
The upside of this is, MyAlertDialog doesn't reference any specific activities. When an activity chooses to use this dialog, it has to implement its contract. That, instead of other way around, where dialog would be coupled with activities and would hook into them.
Calling setRetainInstance(true) will cause the FragmentManager to save your actual Fragment instance. Instead of destroying and recreating the Fragment, it will just pass the same one along to the new Activity.
The main thing to be aware of using setRetainInstance(true) is that one Fragment instance may see multiple calls to onCreateView() and onDestroyView() during the lifetime of the Fragment as it is associated with different Activities. So if e.g. you registered a BroadcastReceiver in onCreateView() and unregistered it in onDestroy(), your code would work fine with setRetainInstance(false), but not with setRetainInstance(true).
Edit - Not only is this answer wrong, but I am in the discussion for the related issue!
So I deserve all the downvotes I get. :) There is a bug on setRetainInstance(true) that you can fix with a gross hack - call getDialog().setDismissMessage(null) inside your DialogFragment's onDestroyView() to hack around it.