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
Related
I have an android application that asks users a few questions, kind of a survey. Each question has an YES and a NO Button. So when the user presses either YES or NO button, a new Button to go to the next question becomes visible over the YES and NO buttons. When the user clicks on the next question button it changes the textview for the question to the next question text as well as the imageView for that question changes.
I am making use of DataBinding along with LiveData.
I have created a list of images used and another list for the questions as follows in my ViewModel class.:
//list of images : Question 4 does not have an image
imgs= listOf(
R.drawable.imgQ1, // 1
R.drawable.imgQ2,// 2
R.drawable.imgQ3)//3
//list of questions
qs= listOf(
"What is .....?",//1
"Who is ....?",//2
"How to .....?",//3
"Why is .....?")//4
I created a ViewState to handle when to show which button and UI:
sealed class MyUIState{
data class UIState(
val policyText: Boolean, // determines if the policy textview should be visible or not
val nextQuestionButton: Boolean,// determines if the next question button should be visible or not
val nextQuestionNewImage: Int = R.drawable.imgQ1,//what image is used for the next question
val nextQuestion: String //what is the next question from the list
) : MyUIState()
}
I also have a BindingAdapter class which handles the visibility and text changes and image changes
(basically this - I have added these function to my layout file with data binding):
#BindingAdapter("btnNextQ")
fun Button.btnNextQ(myState: MyUIState?){
visibility = if (myState is MyUIState.UIState) {
if (state.nextQuestionButton)
View.VISIBLE
else
View.GONE
} else
visibility
}
#BindingAdapter("newImage")
fun ImageView.newImage(myState: MyUIState?){
if(myStateis MyUIState.UIState){
setImageResource(state.nextQuestionNewImage)
}
}
And then in my ViewModel class I have a function for the click event on either the YES or NO Button where I call the UIState like this:
//I have this declared at the top of my class (global)
private val myViewState = MutableLiveData<MyUIState>()
//this the button click for yes:
fun YesClick(){
myViewState.postValue(MyUIState.UIState(
policyText= true,
nextQuestionButton = true,
nextQuestion = qs[0])) //still keep the text for question1
}
Now, I want the image and question to change after the user clicked the next question button. So basically what happens is that after clicking next question, the next question buttons goes invisible as well as the other components keeping the YES and NO buttons and the updated question and image. This is my code for the click event on the next question button:
fun nextQuestion(){
myViewState .postValue(MyUIState.UIState(
nextQuestionNewImage = imgs[1],
nextQuestion = qs[1],
nextQuestionButton = false,
policyText= false))
}
So the state does move to the next question with the necessary items being visible and invisible, but I am struggling to keep it going to the next questions in the list. I can go from question1 to question2 but not sure how to keep doing that until the end of the list of questions.
So basically, I just want to keep updating the same activity with the new data.
Any help or advice will be highly appreciated.
Not sure if I understood your real problem. Maybe if you share more of your code we could help you better.
Anyway, I would recommend you to have two states for your view (activity in your case), one state for the question itself containing both yes and no buttons, question text and the right image. And another state for your "move to next" question which contains the question text, button and whatever you want.
Then your view needs to take care of each state. In the case, you need to deal with one state while dealing against the other state, for example showing one button and hiding the ones you don't need.
So whenever you click on the yes button, the VM will get the event, check everything and then emit an event back to the View with a new state, and so on.
It's quite the same thing you did, but using two states will help you to tackle each state and ease off your code
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.
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.
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.
I'm making a quiz-like game, where user answers each question before they are allowed to go to the next one.
Characteristics of my app:
Each session will have
around 10-30 screens.
In general, the screens are heterogenious in layout
structures, but they can be
classified into 5-6 types.
The expected time that user interacts with each screen is 10-30
seconds
Once user goes to the next screen,
the previous one is not needed
anymore (he never goes back)
I want to have a nice sliding
transition animation when going from
one screen to the next
Implementations I'm considering:
Start a new Activity for each
screen in the 'forwarding' style,
i.e. start the next screen then
finish the current one.
Load all the views before hand and
use ViewAnimator
It looks like none of my current solution is good. Can you help me on a solution that is good in terms of memory consumption, battery consumption, and responsiveness?
Thank you very much.
OK below is what I did. It turned out I manually set the animation
onCreate() {
mAnimation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.slide_in_right);
mViewPool = new View[] { /* A few views for re-using, each of different type */ };
}
proceed() {
nextView = getView(type);
mFrame.removeAllChilds();
mFrame.addView(nextView);
nextView.startAnimation(mAnimation);
}
getView(int type) {
View view = mViewPool[type];
// reset some subviews if neccessary
return view;
}
Where mFrame is whatever ViewGroup you think is appropriate, not neccessarily ViewAnimator. Mine happens to be ScrollView.
If you see any potential problem with this approach, please let me know. Many thanks.