I would like to design a feed-based app with similar design like Facebook, Twitter or Instagram. In the main screen, I have a FeedActivity which holds a ViewPager for holding 3-4 fragments (represented as tabs)
FeedActivity
++ ListOfItemsFragment (there is a ListView holding posts)
++ OtherFragment
++ SomeOtherFragment
I would like to open another activity (or maybe another fragment in ViewPager) when a user touches a post (listed in ListOfItemsFragment). If I open this new activity (PostDetailActivity) and when I return to the FeedActivity, all data is lost in the ListOfItemsFragment because FeedActivity is created again.
Should I create a new Activity for the post detail, or should I add another fragment to FeedActivity independent of ViewPager (I want this fragment to be a popup, not a part of the tabs).
How can I support this behaviour?
Thanks.
Every time you press back the new Activity instance is created. Hense filled data erases. Just type the following lines in manifest file.
<activity android:name=".MainActivity"
android:launchMode="singleTop">
</activity>
Looking at the problem from a different perspective, sqlite is a solution.
Plus, there's an added bonus of storing states even if your app is completely destroyed and has to restart. See this SO thread.
Simply override this method and store that data that you want to display again
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("intTag", intValue); // Save all the value that you want to retain
super.onSaveInstanceState(outState, outPersistentState);
}
and in you onCreate check if the savedInstanceState is not null than get the value and update your
if(savedInstanceState != null) {
int data = savedInstanceState.getInt("intTag");
// Retrieve all the data you have settled there
}
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 an activity MainActivity there are three fragments associated with this activity.
Now one of my fragment Timeline has a listview. Which I populate from a Database in the backend. I use an AsyncTask to fetch values from the DB and process them to the List. I trigger this AsyncTask in the onCreate of the Fragment Timeline.
Now from Timeline on click of any list item I navigate to a different Activity called as DetailActivity
The problem is whenever I press back from the DetailActivity the onCreate of my MainActivity is called and my list refreshes again - the whole DB operation is called again and my list does not retain its state.
I am calculating the visible items of my List before I navigate away from the Fragment but I am forced to use static values for these variables so that I retain the position. How to avoid this?
Below are the snippets of my onPause and onResume as laid down in the fragment Timeline
static int index;
static int top;
#Override
public void onPause(){
System.out.println("onPause");
index = lv.getFirstVisiblePosition();
View v = lv.getChildAt(0);
top = (v == null) ? 0 : v.getTop();
super.onPause();
uiHelper.onPause();
}
#Override
public void onResume(){
super.onResume();
//dbHelper.open();
System.out.println("onResumr");
lv.setSelectionFromTop(index, top);
ActionBar actionBar = getActivity().getActionBar();
actionBar.setTitle("Timeline");
uiHelper.onResume();
AppEventsLogger.activateApp(getActivity());
updateUI();
}
This also forces my AsyncTask to run again and again, which is an overhead.
Edit:
The root of this problem - After struggling for so many days I borrowed a friends phone to test and all was sorted on this new phone. I found out that I had turned on the Do not keep Activities option in my Developer Settings. The Dumb me!!
This is, unfortunately, the default behavior of the Fragment class. A Fragment is destroyed whenever the containing Activity is paused, and recreated whenever the containing Activity is resumed. If you use an Activity instead of a Fragment for the list, you would not experience the same behavior. With an Activity:
AsyncTasks and/or web services would not be called again.
The list would show the previously scrolled position.
If you want the same behavior with a Fragment, you need to override the onSaveInstanceState() method. And while interesting, it is not a small amount of work.
EDIT:
Make sure the Do not keep Activities option is unselected in your phone's Developer Settings. This, though, does not change the essential behavior of the Fragment class that I have outlined above.
You can call setRetainInstance(true) on your fragment. The lifecycle will be slightly different though.
A nice view of a fragment's lifecycle is available here
http://corner.squareup.com/2014/10/advocating-against-android-fragments.html
I've found a lot of questions about that, but none of these can help me.
I have a "MainActivity" which have 4 fragments.
I need to access to one of these fragments, called "my_fragment", in an other simple activity, let's call "SecondActivity".
So, I try to put a property android:tag="my_fragment" in the LinearLayout markup XML of "my_fragment".
And after that, I do that in "SecondActivity":
Fragment frg = getFragmentManager().findFragmentByTag("my_fragment");
... in order to get my fragment. But frg is always null.
I try a lot of others ways, but in vain. This one seems better and easier to do, but perhaps I'm wrong.
Any help would be appreciate. Thank you in advance.
Fabien
EDIT
Since your answers that indicate that's isn't possible, I want to specify what I need.
I just want to get this fragment for reload it. I found something like that on an other subject on Stackoverflow:
frg= getFragmentManager().findFragmentByTag(my_fragment);
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.detach(frg);
ft.attach(frg);
ft.commit();
By the way, I just try to get the fragment in the fragment himself and it's still return null. With the method :
findFragmentById(R.layout.my_fragment)
it's the same result.
And after, I did :
findFragmentById(this.getId())
But it's make an infinite looper, I don't understand the reason...
EDIT2
Alright, let’s give some details :
I have MainActivity with ViewPager with 4 fragments. It’s not fragments at the xml sense. Sorry if I’m confused, I was training to Android very recently and somethings are not clear for me again. So, before yesterday and the read of #Bruce edit, I was thinking that fragments were the components of a ViewPager. So, #Bruce, this is why I can’t use your solution. I was trying to use findFragmentByTag with the tag applicate on my principal LinearLayout markup of my fragment - that is not, I repeat, an xml markup fragment.
This is my approach :
In my MainActivity, I click on the third fragment. I make a research for find some points around me. After an action of the user, still from the third fragment, I open the SecondActivity for authentification and on the user connection, I close this SecondActivity. Now, I need to reload the fourth fragment that will adapt his components in terms of the user situation, while keeping the same state on the third fragment, with points loaded. It’s why can’t use your solution #menion.asamm : I can’t reinstantiate the MainActivity, even if I simulate a click on the third fragment because it will come back in his initial state, without points loaded.
Thank you both of you #Bruce and #menion.asamm for your time in helping me !
Fragments are always owned by one activity, so you cannot directly access a different activity's fragments. The call you are making is looking for fragments within your SecondActivity.
Why do you want to do this? Once some UI is off screen (MainActivity), you usually don't want to do anything with those UI objects, because Android may have removed them from memory. If there is data in "my_fragment" that is needed by SecondActivity, one approach might be to save the data in SharedPreferences or a database in my_fragment, and then load it in SecondActivity.
EDIT
I'm not sure you're getting that it is important which activity you are running in. Here are two options for how to proceed:
If you just want to run the SAME instance of your fragment that was already running inside MainActivity, then maybe what you want to do is finish your SecondActivity to return to MainActivity.
If you want a NEW copy of the same fragment inside SecondActivity, then you can include the fragment inside SecondActivity's layout (or add it to some container later).
Also, notice that for your call to findFragmentById, the ID needs to be the ID that was specified in the layout file as the value of android:id (not the R.layout.my_fragment). It might be better to use a fragment tag, which you can either specify in your layout file or when you add the fragment.
Mainly I think you need to read Google's guide on fragments.
EDIT2:
Ah, I see, I have a similar fragment-refresh situation in my app. You basically need to get data from SecondActivity back to the fragment inside MainActivity. The approach I use is this:
Save the data from SecondActivity in storage (DB or SharedPreferences).
Finish SecondActivity so that MainActivity and your fragment are shown again.
Override onResume in your fragment to fetch the data you saved in SecondActivity.
Another option is to launch SecondActivity using startActivityForResult, and then process the results in MainActivity, passing them to its fragment.
Regarding how to find the fragment by tag, you first need to set the fragment's tag. If you are declaring your fragment in a layout XML, then you can do it there (and you can also declare
<fragment class="com.xyz.MyFragment"
android:tag="MyFragment"
android:id="#+id/my_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Now from inside MainActivity you can either do findFragmentById(R.id.my_fragment) or findFragmentByTag("MyFragment").
If you are NOT declaring the fragment in XML, but adding it directly, you can set the fragment's tag as part of the add call:
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.frag_parent, new MyFragment(), tag);
EDIT3: Ah, you're using ViewPager to hold fragments. Now I understand better. They're still fragments, but getting access to them is indeed tricky, because Android constructs a fragment tag in some internal code. Here is another SO question on this issue:
Retrieve a Fragment from a ViewPager
Hmm if you really need just refresh of fragment attached to different activity, I suggest:
first activity start second activity with
startActivityForResult(intent, MY_CODE);
second activity when wants to refresh fragment in first activity, finish it's state with
Intent data = new Intent();
data.putExtra("REFRESH_FRAGMENT", true);
setResult(RESULT_OK, data);
finish();
back in first activity, you may catch this result by
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// check request code
if (requestCode == MY_CODE) {
// check result
if (resultCode == RESULT_OK) {
// check data
if (data != null && data.getBooleanExtra("REFRESH_FRAGMENT", false)) {
refreshFragment();
}
}
}
}
Possible?
I am trying to implement orientation feature in the app. The app is basically made of tabs, using TabHost. Each tab is an activity group with a few activities. Each of this child activity runs a background task using AsyncTask to pull out server data and render the UI.
For instance,
HomeActivity- tab activity class that creates all the tabs
UserActivity- an activity group class representing a tab (say Tab 1)
TabOne- child activity inside UserActivity group
User sees Tab 1 soon after successful login. Issue begins when I tried to set orientation capabilities to this tab. Within the activity (TabOne), I added the following
#Override
public void onSaveInstanceState(Bundle outState) {
System.out.println("saving state..!");
outState.putStringArray("imageUrls", imageArray);
super.onSaveInstanceState(outState);
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
System.out.println("restoring state..!");
imageArray = savedInstanceState.getStringArray("imageUrls");
super.onRestoreInstanceState(savedInstanceState);
}
I am able to obtain 'imageUrls' state. But, later down, I could not access the static tab host variable.
HomeActivity.tabHost.getTabWidget().getChildAt(1)
tabHost variable is null! I am not saving any of its state. How could I possibly save tabHost instance?
While researching other community posts, I found 'onRetainNonConfigurationInstance()'. I implemented the same.
public Object onRetainNonConfigurationInstance() {
System.out.println("retaining state..!");
return HomeActivity.tabHost;
}
Bu this object is returned as null. Can anyone walk me through steps to handle orientation changes in apps with tabHost?
Ask me if you find anything confusing.
Thanks in advance!
SOLVED: Adding screenSize to activity tag in the manifest fixed the issue.
<activity android:name=".HomeActivity"
android:configChanges="orientation|screenSize"/>
You can add the parameter
android:configChanges="orientation"
to your activity in manifest. I think it does what you want. It should look like:
<activity
android:name="com.your.package.YourActivity"
android:configChanges="orientation"
</activity>
How do I retain the state of an activity in android? I have two layouts for portrait and landscape in layout and layout-land. I am loading the value from service at the time I am showing progress dialog. If loaded user rotates the device to landscape at the time also loading. How do I avoid that? user typed content in webview that also refreshed. How do I avoid that, can anybody provide an example?
Thanks
When orientation changes, the Activity is reloaded by default. If you do not want this behavior then add this to the Activity definition in your manifest:
android:configChanges="orientation|keyboardHidden"
For more detail, see Handling Runtime Changes
You can use the onRetainNonConfigurationChange() callback to store arbitrary data. It is called just before your application is about to be recreated.
Then, in onCreate() just check if some data were put aside by calling getLastNonConfigurationInstance() that returns the Object you put aside or null.
See this article on android developers.
Here's a sample borrowed from the link above:
#Override
public Object onRetainNonConfigurationInstance() {
//this is called by the framework when needed
//Just return what you want to save here.
return MyBigObjectThatContainsEverythingIWantToSave;
}
Automagic restore of previously saved state:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final MyDataObject MyBigObjectThatContainsEverythingIWantToSave = (MyDataObject) getLastNonConfigurationInstance();
if (MyBigObjectThatContainsEverythingIWantToSave == null) {
//No saved state
MyBigObjectThatContainsEverythingIWantToSave = loadMyData();
} else {
//State was restored, no need to download again.
}
...
}
When orientation changes, the Activity is reloaded by default. If you do not want this behavior then add this to the Activity definition in your android manifest file :
android:configChanges="orientation|screenSize|keyboardHidden"