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!
Related
I am developing the detail view of a product which gives suggestions of similar products (waking the same activity with another product who also has suggestions) more or less like Google Play app detail activity. The problem comes when accessing to multiple related products which causes a OutOfMemoryException (since we are keeping all the previous instances of that ProductDetailActivity on the BackStack).
Is there a way of provide proper products back history without having to keep all the previous activities consuming memory?
I've checked Google play app and I'm not able to see how but it seems that it keeps all the previous activities and it's not causing any OutOfMemory while navigating related apps over related apps.
I had a similar issue. In my case I was using fragments, but I will share the technique so you can use it with your activity.
You can think of the multiple activities like a stack. When the user selects a product suggestion, the current activity is pushed onto the stack and a new activity is started. Conversely, when the back button is pressed, the activity finishes and the previous activity is popped off the stack and made current.
This can consume a lot of memory depending on your application.
The insight that I had was that I could save a lot on memory by managing that stack myself, and only saving the minimum amount of data required to recreate that UI.
So let's start by creating the stack:
private ArrayList<Bundle> mProductStack;
You will need to persist this stack in onSaveInstanceState() and recover it in onCreate(). Let's get those out of the way:
#Override
public void onSaveInstanceState(Bundle outState) {
// ... all your other state saving code
outState.putParcelableArrayList("productStack", mProductStack);
super.onSaveInstanceState(outState);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ... all your other activity create logic
if (savedInstanceState != null) {
mProductStack = savedInstanceState.getParcelableArrayList("productStack");
} else {
mProductStack = new ArrayList<>();
}
}
There's a reason for using a list rather than a stack. I had a lot of problems with persisting/unpersisting a Stack<Bundle> around casting and so forth, so I decided to go with the simple ArrayList<Bundle>, which is supported directly by Bundle (putParcelableArrayList(), etc).
Now when the user navigates to a new product, you need to save the current state in the stack, and load your new product into the current activity. Your state might even be as simple as a product ID:
Bundle entry = new Bundle();
entry.putInt("productId", mProductId);
mProductStack.add(entry); // push
// ... load the data for the next product
You may need to persist some actual UI state, for example I had this:
int scrollPos = mNestedScrollView.getScrollY(); // remember where we were in the list
entry.putInt("scrollPos", scrollPos);
Now you just have to handle the back button press:
#Override
public void onBackPressed() {
if (mProductStack.isEmpty()) {
super.onBackPressed();
return;
}
Bundle entry = mProductStack.remove(mProductStack.size() - 1); // pop
mProductId= entry.getInt("productId");
// ... load the data for the previous product
}
I don't know how complex your state is, but keep in mind that all the data types you want to save will need to implement Parcelable. There's a lot of content online (including SO) on this topic.
The thing that seems to have the most impact on memory is images. Memory management for bitmaps is a whole nasty topic unto itself. You may want to implement some type of LRU cache for images so you don't have to re-fetch images for the previous product — unless memory constraints force you to do so.
I want to develop an application that, at the beginning, checks a variable's value and, basing on this value, starts the activity A or the activity B, something like this:
protectec void onCreate(...){
boolean b = checkVariable();
if(b){
startActivityA();
} else {
startActivityb();
}
}
What I'm doing
This is the method I have currently implemented:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_limitation);
varChecker = new VarChecker(this);
if(varChecker.getRemainingUses() <= 0){
limitReached();
} else if(varChecker.isFirstTime()){
firstTime();
} else {
startMainActivity();
}
}
This is an activity that must be shown the first time the application is executed. Else, depending on the getRemainingUses() result, it must start the activity A or the activity B automatically.
The question
Is there any way to do what I want, without the need of create a new Activity, specially to avoid the super.onCreate(savedInstanceState) and the setContentView(R.layout.activity_limitation)?
Is there any way to do what I want, without the need of creating a new
Activity, specially to avoid the super.onCreate(savedInstanceState)
and the setContentView(R.layout.activity_limitation)?
I had a similar problem some time ago. As far as I know, It's not possible to avoid calling super.onCreate(savedInstance) and setContentView(R.layout.activity_limitation). It's design of the Android activity.
I see the following possible solutions:
if you want to choose an appropriate activity by e.g. clicking a
button, then you just need to create a new Intent basing on a
variable and there's no problem.
if you want to choose an activity in
a different moment of the flow - e.g. during the application start you can:
create a single activity for two options, use fragments and switch between them
create "dummy" activity with an empty layout, create an intent and switch to an appropriate activity basing on a variable and finish "dummy" activity. Please note that this solution is a kind of workaround and it's worth to spend some time to figure out something better, but I'm not sure if there is a recommended solution for such use cases.
I'm a beginner in Android, so I apologize for the mistakes and I'd appreciate any constructive criticism.
I'm writing a basic application with a ListView of images, and when the user clicks on an item in the list, I want to display that image in a ViewPager, where the user can swipe back and forth to browse the whole list of images. Afterwards when the user presses the back button, I want to switch back to the ListView.
I manage the business logic in the MainActivity, which uses MainActivityFragment for the ListView and ImageHolderFragment for ViewPager.
The simplified code so far is as follows:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListItems = new ArrayList<>();
mListItemAdapter = new ListItemAdapter(this, R.layout.list_item, R.id.list_item_name, mListItems);
mListView = (ListView) findViewById(R.id.list_view_content);
mListView.setAdapter(mListItemAdapter);
mDeletedListItems = new ArrayList<>();
mViewPager = (ViewPager) getLayoutInflater().inflate(R.layout.image_display, null, true);
mImageAdapter = new ImageAdapter(getSupportFragmentManager(), mListItems);
mViewPager.setAdapter(mImageAdapter);
mViewPager.setOffscreenPageLimit(3);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mViewPager.setCurrentItem(position);
setContentView(mViewPager); // TODO: this is very wrong!
}
});
loadImages();
noContentText = (TextView) findViewById(R.id.no_content_text);
if (mListItems.isEmpty()) {
noContentText.setText(R.string.no_images);
} else {
mImageAdapter.notifyDataSetChanged();
}
}
Although this does work to some extent, meaning that it manages to display the ViewPager when an item in the list is clicked, there are two things about it ringing the alarm bells:
I've read that calling setContentView() for the second time in the same class is pretty much a sin. Nobody explained me why.
The back button doesn't work in this case. When it's pressed, the application is terminated instead of going back to the list view. I believe this is connected to the first point.
I would appreciate any help, explanations if my idea is completely wrong, and if my case is hopeless, I'd like to see a successful combination of ListView and ViewPager with transitions between each other.
Your activity already has R.layout.activity_main set as content view, which rightly displays the list view - that's what the responsibility of this activity is as you defined it. If we want to change what's shown on the screen, we should use a different instance of a building block (activity or fragment) to display the view pager images.
To say the least, imagine if you wanted to change the view to a third piece of functionality or UI, or a fourth... it would be a nightmare to maintain, extend and test as you're not separating functionality into manageable units. Fields that are needed in one view are mixed with those needed in another, your class file would grow larger and larger as each view brings its click listeners, callbacks, etc., you'd also have to override the back button so it does what you want - it's just not how the Android framework was designed to help you. And what if you wanted to re-use UI components in different contexts whilst tapping in to the framework's activity lifecycle callbacks? That's why fragments were introduced.
In your case, the list view could continue to run in your MainActivity and in your click listener, onItemClick you could start a new activity that will hold a viewPager:
Intent i = new Intent(MainActivity.this, MyLargePhotoActivityPager.class);
i.putExtra(KEY_POSITION, position);
// pass the data too
startActivityForResult(i, REQUEST_CODE);
Notice how you could pass the position to this activity as an int extra, in order for that second activity to nicely set the viewPager to the position that the user clicked on. I'll let you discover how to build the second activity and put the ViewPager there. You also get back button functionality assuming your launch modes are set accordingly, if needed. One thing to note is that when you do come back to the list View, you'd probably want to scroll to the position from the view pager, which is why you could supply that back as a result via a request code. The returned position can be supplied back to the list view.
Alternatively, you could use the same activity but have two fragments (see the link further above) and have an equivalent outcome. In fact, one of your fragments could store the list view, and the second fragment could be a fullscreen DialogFragment that stores a viewPager, like a photo gallery (some details here).
Hope this helps.
I've read that calling setContentView() for the second time in the
same class is pretty much a sin. Nobody explained me why.
Well, you kind of get an idea as to why.
When you use setContentView() to display another 'screen' you do no have a proper back stack.
You also keep references to Views (like mListView) that are not visible anymore and are therefore kind of 'useless' after you setContentView() for the second time.
Also keep in mind orientation changes or your app going to the background - you'll have to keep track of the state that your Activity was in which is way more complicated than it has to be if you have one Activity that does two different things.
You won't be arrested for doing things like you do right now, but it's just harder to debug and keep bug free.
I'd suggest using two different Activities for the two different things that you want to do, or use one Activity and two Fragments, swapping them back and forth.
If you insist on having it all in one Activity you need to override onBackPressed() (called when the user presses the back button) and restore the first state of your Activity (setContentView() again, pretty much starting all over).
I am calling an activity from within itself - basically i've a list of new storys and two filter buttons that when clicked restart the activity with an intent passed that changes the news stories.
When i run the app it works, but for a second i get the old activity UI while the app reads from the new xml feed and then the UI updates. Is there any way to stop this from happening and get the activity to restart cold.
here's the code I am currently attaching to the onclicklistener
public void openFootballNews(View v) {
Intent i = new Intent(this, News_Landing.class); // News_landing class is the class this code is in
Bundle bundle = new Bundle();
bundle.putString("code", "football"); // this, if set, changes the xml feed to read
i.putExtras(bundle);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
this.onCreate(null); //this has halved the time the old UI is on the screen for but I cant get rid of it completely
startActivity(i);
}
any help would be great, thanks!
Starting an activity from itself doesn't make much sense (unless your aim is to do something esoterically recursive ;) ). Also, I may be mistaken, but I believe activities are kept in a stack so that as you flip between news stories, you're piling up one nearly-identical activity after another. I'd similarly think calling onCreate() by hand is bad form.
Would need to see all of your code, but my guess is that you are reading your feed and creating your list inside onCreate(), and that your best bet is to refactor that into a openNews(String sport) method, which you call once in onCreate() and again in your listener(s).
I've put a little app together that has three tabs to show three different web pages. It does work however I am bit worried I haven't got enough control over how this whole thing works. When I click a tab, I get a web page loaded (see code sample below), now when I click another tab another page loads in another view. When I go back to the first tab, the whole thing get initilized again and the page loads. Is there a way how I can control this and keep the underneeth tab's activity in its current state as long as I want (and say only "refresh" the page when it changes).
do I need to handle onPause()/onResume() methods for that or instead implement my tabs as views of a single activity (is this possible at all?)? How do I store the state of my activity to avoid re-initializing it every time?
this how activities are hooked to tabs:
intent = new Intent().setClass(this, tab_schedule.class);
spec = tabHost.newTabSpec("Schedule").setIndicator("Schedule",
res.getDrawable(R.drawable.tab_icon_schedule)).setContent(
intent);
tabHost.addTab(spec);
the tab_schedule.class does a simple web page load:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tab_people);
try {
WebView currentView = (WebView) findViewById(R.id.tab_people_WebView);
currentView.getSettings().setJavaScriptEnabled(true);
currentView.loadUrl("http://pda.lenta.ru");
} catch (Exception e) {
Log.v("webClientInit", e.getMessage());
}
}
If you don't want to create a new activity for each tab, you can use a TabWidget with a FrameLayout to toggle between views.
As to switching activities, see this question for a way to not recreate the activity each time.
Regardless, you should always implement onPause and onResume to restore the state of your app. You might want to read up on the Activity Lifecycle, but basically you cannot prevent your activity from being killed if goes into the background. Thus, you should store your state in onPause. The link above has some info on how to do so as well.
To bring the previous activity to the top of the stack use intent.addFlag(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);