Ok I am designing an app which has a very simple layout of 62 Content Screens, accessed via 27 Menu Screens. In total this is 89 activities.
At the moment I have each Content Screen to be an Activity that just calls an XML layout (some text, buttons and image) and adds some onClick functionality.
Each Menu Screen is a ListActivity and pressing each item in the list opens that item's Activity (be it another Menu Screen or a Content Screen).
At the moment I am testing it with 3 Menu Screens and a Content Screen. It is fine scrolling through them all, however I am concerned that when I finish the app it will have too many activities and make the app slow and slugish. In an average use of the app I imagine the user would use no more than 10 activities but I am not sure if Android would be anticipating creating the other unused Activities or what.
So another way to implement this would be to have just a few Activities (maybe 1 for a MainMenu, 1 for a subMenu and 1 for a ContentScreen) that just work out which layout to display. However I feel this would mean that each Activity has a hell of a lot more work to do and also I lose the functionality of pressing the Back button taking the user back up through the menu hierarchy (and coding a replacement Back button onto each Screen would involve a massive Case for the onClick involving every single possible layout, all 89 of them!).
What would be the best way to go about this app?
Any help is very much appreciated
Unless layouts and behaviours differ significantly between menu activities and content activities, I'd go with "just a few activities approach".
Back button won't break, Android keeps track of your backlog.
Do you really need 89 different layouts? If it's mostly content what's different between them, store content in a database or file or in /res/, reuse layouts and fill content areas of layout at runtime.
Updates:
Wouldn't it be easiest to have 1 activity per content page that in onCreate() fills in the layout with the appropriate content?
Depends on how different your onCreate() code is going to be. If your onCreate() is going to be a giant switch with 62 wildly different case clauses, then it's probably better to go with separate small activities, not a huge one. If you can generalize the onCreate() code, and keep it below, say, 100 lines and 10 branch statements, that'd be way to go.
To give an example, some time ago I was building a simple app that has a collection of exam questions, and presents them randomly to user. Each question has question text, illustration and 2-4 answers choices. There were around 500 different questions. Here's the code that loads a question from database and updates the layout. Notice that it handles variable number of answers, and possibility of some questions not having illustration.
public void loadQuestion(int id) {
// Columns in questions table:
// CREATE TABLE questions (
// id integer,
// q text,
// a1 text,
// a2 text,
// a3 text,
// a4 text,
// a5 text,
// correct integer,
// img blob
// );
Cursor c = mDatabase.rawQuery("SELECT * FROM questions where id=" + id, null);
c.moveToFirst();
TextView text = (TextView) findViewById(R.id.text);
text.setText(c.getString(1));
RadioGroup g = (RadioGroup) findViewById(R.id.answers);
g.clearCheck();
// Load answers!
int correct = c.getInt(7);
int[] buttons = new int[] {R.id.a1, R.id.a2, R.id.a3, R.id.a4, R.id.a5};
for (int i=0; i < 5; i++) {
String answerText = c.getString(i + 2);
RadioButton rb = (RadioButton) findViewById(buttons[i]);
if (answerText != null && answerText.length() > 0) {
rb.setText(answerText);
rb.setTag(i + 1 == correct ? "correct" : null);
rb.setVisibility(RadioButton.VISIBLE);
} else {
rb.setVisibility(RadioButton.GONE);
}
}
byte[] encoded = c.getBlob(8);
ImageView iv = (ImageView) findViewById(R.id.image);
if (encoded != null && encoded.length > 0) {
iv.setVisibility(ImageView.VISIBLE);
iv.setImageBitmap(bytesToBitmap(encoded));
} else {
iv.setVisibility(ImageView.GONE);
}
}
What do you mean by: Back button won't break, Android keeps track of your backlog?
As the user moves from activity to
activity, across applications, the
Android system keeps a linear
navigation history of activities the
user has visited. This is the activity
stack, also known as the back stack.
In general, when a user starts a new
activity, it is added to the activity
stack, so that pressing BACK displays
the previous activity on the stack.
(from Activity and Task Design Guidelines)
I don't think it will become slow just because of the number of declared activities.
However you should probably reuse some of your layouts across activities: http://developer.android.com/resources/articles/layout-tricks-merge.html
you can reuse your layout and can use startAcitity and startActivityForResult methods to manage the activities effectivity.
Related
Ok, I was complaining about this a few days ago, now I have some code in from following a few tutorials/lessons.
I have a RecyclerView that contains an ArrayList with cards, those have an image, title and description.
I have OnItemClick for those cards, using Parcelable when a card is clicked a second activity opens and shows the full details from the cards - that image, title and description, with a new layout.
I've added a previous and a next button bellow that.
Edit to clarify Struggling with:
I need to have a button in the second activity, the one that opens when clicking on a specific card, and clicking on that button should bring the details from the next card from the first activity. Currently a user would have to go back from the second activity to the recyclerview and click on the next card to see the full details from it
I tried implementing a couple of things I found in older questions here but since they're mostly just code based on somebody else's specific situation I can't seem to edit them in order to get them to work.
I'm adding the link to the repository, it's a bit messy since I'm testing a bunch of things in it, but I'm leaving it for reference: https://github.com/simplydikka/traffic.git
The activities I'm working with currently are those:
Details from list gist
The only walk-around I could find based on my level was to set the click of each card to lead with an if statement to a specific tab from a tabbed activity, but that would mean I will have a very very long list of fragment layouts with different information in them by hand, and that just can't be the answer for something like that. I got that to work but it makes no sense to put that much content by hand when I've already got it to show based on position
EDIT: I got to here:
Intent intent = getIntent();
final ExampleItem exampleItem = intent.getParcelableExtra("Example Item");
position = intent.getExtras().getInt("Position");
final int imageRes = exampleItem.getImageResource();
final String line1 = exampleItem.getTextTitle();
final String line2 = exampleItem.getmTextDescription();
final ImageView imageView = findViewById(R.id.image_activity2);
imageView.setImageResource(imageRes);
final TextView textView1 = findViewById(R.id.text1_activity2);
textView1.setText(line1);
final TextView textView2 = findViewById(R.id.text2_activity2);
textView2.setText(line2);
Button next = (Button) findViewById(R.id.next);
next.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
position = position +1;
imageView.setImageResource(imageRes);
textView1.setText(line1);
textView2.setText(line2);
}
});
This is in my second activity class, the one that is showing the details and buttons. I got the button to work. I just don't get how to attach a position to the resources. I did the dumbest thing just to see if it would work at all, and when I wrote (imgRes+1) on click the image becomes a black square, so it does affect it finally. I just need to find a way to actually bring the next position. I'm still looking, testing and searching, definitely not sitting around and waiting for somebody to solve it for me, but it would be reeeally cool if anybody passing by could hind me :D
I have an enhanced loop, which will dynamically inflate however many layouts relevant to the number of values held in my array.
This works perfectly however, there is a method being called on each iteration, which also works but there is a big bug that I need help resolving.
Imagine there are 5 items in my array, therefore 5 layouts are inflated, in these layouts there is a little scratchcard type section on the layout.
Now if the user is on page 1, uses the scratchcard, then moves on to page 2, uses the scratchcard etc etc, it works fine.
But if the user is on page 1 and then goes to say, page 5 and then back to page 1 (basically in a random order), the scratchcard doesn't work.
From my understanding, the reason for this is that the method is being called an implemented on each iteration and the view is losing its state if the user scrolls back or scrolls in random orders.
Therefore I need a way to save the created view state in my viewpager.
Is this possible for my scenario? I have tried my best to find a solution, but cannot find something that feels relevant to my question.
Here is a snippet of the code in question. Thanks for any guidance or suggestions!
for (String x : array1) {
//loop out the number of layouts relative to the number of questions held in x
View current_layout = LayoutInflater.from(getActivity()).inflate(R.layout.question_fragment, null);
//use the pageAdapter to add the layout to the users view
pagerAdapter.addView(current_layout);
//call method to add functionality to the scratchcard
isCorrect(current_layout);
}
public void isCorrect(View current_layout) {
ScratchoffController controller1 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view1), current_layout.findViewById(R.id.scratch_view_behind1));
ScratchoffController controller2 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view2), current_layout.findViewById(R.id.scratch_view_behind2));
ScratchoffController controller3 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view3), current_layout.findViewById(R.id.scratch_view_behind3));
ScratchoffController controller4 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view4), current_layout.findViewById(R.id.scratch_view_behind4));
}
I ussually use ViewPager with Fragments and what you mention has happend to me when I try to keep references to the Fragment instances (in my case) outside of the viewpager.
This happens because the viewpager may create new instances of the Fragment it contains when you re-vist the tab in the way you mention. When this happens, the instance reference you hold outside of the viewpager is not anymore what the viewpager is showing.
In your case , according to this question, you have to oveeride instatiateItem and destroyItem. I think you can use these methods to save state restore state, and also you could update any external reference when instantiateItem is called.
So basically my app works like this. There's a list of Physics equations to choose from. Each equation (i.e: Vf = Vi+at) has it's own entire activity (uses both xml and java). Here's an example of how an activity looks like for an equation:
(http://i.imgur.com/Jx1VIVX.jpg)
So let's say that I want to create 100 equations for my app. Would I have to create 100 separate activities as well?
-If yes, then would it affect performance, and by how much would the size of the apk file increase? Is there a simple way to categorize the activities into directories?
-If no, then how would I combine those "100 separate activities" into fewer activities?
So far I have only done 3 equations and I have created a separate activity for each. Here is how the selection page looks like.
(http://i.imgur.com/M3J332n.jpg)
Example:
If I select the "Solve for Final Velocity" option from the spinner, it will do the following command and opens up the activity for the selected equation.
//What happens when user chooses a category
if(position == 1){
startActivity(vf);
Instead of calling multiple Activities, try using the same Activity and Manipulate the views and its contents accordingly.
Example
If user selects the "Solve for Final Velocity" option from the spinner, it will do the following.
Intent i;
if(position == 1){
i = new Intent(this,SecondActivity.class);
i.putExtra("Action","Equation1");
startActivity(i);
}else{
i = new Intent(this,SecondActivity.class);
i.putExtra("Action","Equation2");
startActivity(i);
}
...
In your SecondActivity.java
String input1,input2, formula;
String Action = getIntent().getExtras().getString("Action");
//now you have "What to do solved"
switch(Action){
case "Equation1" :
//manipulate your formula for Equation1
break;
case "Equation2" :
//manipulate your formula for Equation2
break;
}
You can even hide particular Views if you don't need them for a particular Equation.
Hope this helps.
I have an activity that extends ListActivity, a list of "concepts" (let's call this list "C") and an onItemClickListener defined for this list. Whenever I click a "concept", no matter which one, the app must display another list. I have the following code to change the displayed list:
if(position == 0) change_list("adapter1");
else if (position == 1) change_list("adapter2");
else if (position == 2) change_list("adapter3");
else if (position == 3) change_list("adapter4");
else if (position == 4) change_list("adapter5");
Where position is the position of the clicked element in C
The function change_list performs setListAdapter(parameter) depending on the parameter I pass.
If I click the first element of C (the first concept), a list related to the first concept must appear. However, after calling setListAdapter(adapter), the data related to this concept is displayed, and also part of the C's list data.
For example: let's suppose C has these concepts:
A B C D E
and I click "A", which would lead to display a list with the following data: {a1,a2}
That's the final result:
a1 a2 C D E
And then, when I interact with another element on screen or I scroll down the list, the "ghost" data disappears and only the correct data remains on screen, just like this:
a1 a2
To make things worse, when I want to display list C again, nothing strange happens. Everything is displayed correctly.
At any time incorrect data is stored where it doesn't have to. One function my app must allow is to generate a txt file , and the generated txt file contains exactly the data I introduced. No data is corrupted or duplicated. I also tried using notifyDataSetChanged() and other functions, but I didn't solve the problem.
EDIT :
Here goes the xml code used for the main list of the activity:
<ListView
android:id="#android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#FF0000"
android:layout_below="#+id/afegir"/>
And an example of code in which I determine which contents must be displayed on screen:
else if(comprovar_concepte_actiu() == 1){
pnt = mydbhandler.getStoredValues("despeses1");
pnt.moveToFirst();
if(pnt.moveToFirst()){
do{
adapter_mostrar.add(pnt.getString(pnt.getColumnIndex("nom")));
}while(pnt.moveToNext());
}
adapter_mostrar.notifyDataSetChanged();
}
Where comprovar_concepte_actiu() returns and integer that tells which concept has been clicked in the main list C and adapter_mostrar is the single adapter I'm using now, instead of using multiple adapters (which made me use setListAdapter)
At the beginning of the activity, I call this.setListAdapter(adapter_mostrar). That's all I have.
EDIT 2 :
https://www.dropbox.com/s/7twgy043lkxb2x5/conceptes.java?dl=0
Here is a link to my conceptes.java activity. Press CTRL+F once opened and search "this is where I call.. " and you will directly get to the function where the change of list displayed on screen starts
I haven't found a solution yet. Any idea will be totally appreciated
The problem here is that - when you set a new adapter - the old data is still drawn. In other words, there has been no command to "refresh" the listView. However, the new adapter will be commanded to draw its own views. What ultimately occurs is that the old items are still there, the new items are redrawn, but when scrolled away the new adapter won't redraw/recreate the old items.
The solution is to simply refresh the adapter. However, there are two ways to go about this:
Add a new adapter every time and use myListView.invalidateViews(); or something similar [This is probably the easiest solution to implement, although probably not the best in the long run]
Change the dataset of the adapter and use notifyDataSetChanged() [on the adapter]
The latter option is a far better idea. You should use a single adapter and simply change its data over time. Once its dataset is changed, then tell the adapter that such a thing happened so it refreshes. However, you should read more here on all the different thoughts and processes about it, rather than take my opinion on it.
Edit:
There's apparently some very nicely, thought out answers around. Here's another one, that tells you more specifically about the differences between these two:
Is there any difference between ListView.invalidateViews() and Adapter.notifyDataSetChanged()?
Edit2:
With the onClickListener in mind, invalidateViews() will most likely not work, as it'll probably still draw the old views to "finish" the click (ie, draw the highlighting).
Changing the data directly inside a single adapter and using Adapter.notifyDataSetChanged() is your best bet, as it'll know to redraw everything from a single adapter and use only the current data defined by this single adapter.
Best to leave the data specifics (and defining what to draw based off of that data) up to what actually knows the data, rather than a higher up container that knows nothing specific about the actual data.
I'm new to Android and I'm building a simple application to start with. It consists of a client with three screens. In the first screen the user is prompted for an Ip to connect to a server (I use an EditText and a button). If the connection is successfully established, some data will be retrieved from the server and the client will show the data on a blank screen (I use a TextView). This would be the second screen. Then, the user could ask the server for detailed information about any data that has been retrieved from the server, which would be the third screen (I use a TextView again).
The problem is that I don't know what's the best way to go about it. I have currently one activity and one XML file containing all the components of the view (EditText, button, TextView). Until now, I've been using setVisibility(View.GONE);to hide certain components depending on the screen the user is in. (For example in the first screen I have to hide both TextViews).
One of the problems I'm facing is that when I put the phone in a horizontal position the components I had hidden show up again. I don't know if hiding views is the ideal thing to do for my purpose.
I've thought that maybe I should use more than one activity, shouldn't I?
I really appreciate any help you can give me to structure my first app.
I would definitely recommend splitting up your App into multiple Activities/Fragments. Depending on how big the logic for each screen gets you will be glad you did it later on because each Activity only has one responsibility.
Look at your mail app for example. You got your List Activity showing you all your mails and then when you select one it starts the Detail Activity showing you the content of your mail. Each Activity is only responsible for one thing which make each one easier to write and maintain.
It also simplifies your layout definitions because each one only contains the relevant parts.
Seems like this is coming up a lot. Android destroys and recreates and Activity when the configuration changes. Screen rotation is part of the orientation. In order to avoid that, the Activity is responsible for retaining state. The mechanisms given for that are the onCreate and onSaveInstanceState. In your example, you could do something like the following:
int uiPhase = 1;
#Override
void onCreate( Bundle data ) {
uiPhase = data.getInt( "uiPhase", 1 );
// inflate layout
setPhase( uiPhase );
}
// invoke the following each time your screen changes
void setPhase( int newPhase ) {
uiPhase = newPhase;
switch( uiPhase ) {
case 1: // show UI elements for first screen, hide others
break;
case 2: // show UI elements for second screen, hide others
break;
case 3: // show UI elements for third screen, hide others
break;
}
}
#Override
void onSaveInstanceState( Bundle data ) {
data.put( "uiPhase", uiPhase );
}
I didn't want to complicate the pattern above too much, but a good method for setting visibility is as follows:
phase1view.setVisibility( uiPhase == 1 ? View.VISIBLE : View.GONE );
phase2view.setVisibility( uiPhase == 2 ? View.VISIBLE : View.GONE );
phase3view.setVisibility( uiPhase == 3 ? View.VISIBLE : View.GONE );
That pulls the repetition in the setPhase method quite a bit together.
Set button visibility to GONE (button will be completely "removed" -- the buttons space will be available for another widgets) or INVISIBLE (button will became "transparent" -- its space will not be available for another widgets):
use in place of
setVisibility(View.GONE)
change to
setVisibility(View.INVISIBLE) and try