I've searched at length and tried the usual suspects, but it's time to ask for help.
My Android activity loads EditText, Spinner and CheckBox values from SQLite happily enough.
The problem is sensing if the user thereafter actually changes a value so I can warn at onBackPressed if unsaved changes were made (i.e. s/he had not hit the 'Save' button).
For example, with an EditText I've tried:
using a TextWatcher.onTextChanged listener, but it gets fired when the EditText is first instantiated, which makes setting a 'data changed' flag meaningless
trapping the various KEY_DOWN events for the DONE, ENTER, NEXT and DEL keys, but with mixed results that wouldn't help with my Spinners and CheckBoxes anyway
onTouchListener (I think, but I'm a little foggy by now), to no avail
I guess what I'm looking for is a View class listener, attached only after the activity has settled down, that fires for subsequent state changes. But, nothing I've searched for fits the bill.
How do others handle this problem?
Thank you for any guidance.
I can't think of any general mechanism that could apply to all kinds of widgets.
Nevertheless, a cleaner solution would be :
to have a model (POJO) that represents all data you display on screen
every change in widgets are reflected in the model
every time a setter is called in the POJO, it compares the new set value with the old one
if a change is made, then it raises an internal flag.
if the flag is raised at onBackPressed, then you show an AlertDialogFragment asking whether changed values should be saved or not.
Related
I have a screen listening for a data class that contains everything I need. ScreenState. Whenever the user press a button I send the event to a ViewModel. This specific event is just getting the intent and setting on the ScreenState parameter like this.
screenStateFlow.emit(
ScreenState(
Intent(...)
)
)
What happens there is, first time works (User leaves the app and then comeback to the app). When user comebacks to app and there's not any data from the intent and want them to be able to start an intent again. So it does the same action.
Triggers a specific event which gets the intent and sets on the ScreenState parameter and this value is emited, again
And here lays the problem. Value is the same. So compose doesn't recompose itself.
And this solution works. You could say that I don't need all of this and it could work by just starting the intent without having to go through the event process and etc.. But I want it that way (unless I don't find a proper solution)
screenStateFlow.emit(
ScreenState(
Intent(...),
!triggerRecompose
)
)
Is there any better solution?
Edit: Someone having the same issue as me, the provided answer didn't work. I've already tried the MutableState and the State from compose in ViewModel. Didn't work
I had a similar issue in which I wanted to trigger a snackbar even if the value is repeated.
I solved it by adding a variable parameter to my message object (such as timestamp or Math.random()).
In this way, even if the message content is the same, the Message object is different and it triggers a state change.
I'm working on my first app that uses RecyclerView and while I am making great progress, I strongly suspect that my design needs some changes.
I've asked in several places over recent weeks for complete examples of a RecyclerView that read, update, insert and delete and have come up empty so I'm guessing my way along based on various passing remarks. First, let me explain concisely how I have designed this app. I imagine this will make my mistakes self-evident.
My RecyclerView is based around sales in the small company where I work. Each sale consists of a client code, an order number, an order date, a number of tickets sold, and a seller name. My design uses a listener to react to a FAB (FloatingActionBar) and, if pressed, goes to an Add activity that prompts the user for the information needed to create a new sale. No information is passed to this activity because none is needed. The Add activity validates the data and, when the user presses an Add button, that new data is passed back to my main activity. The main activity inserts a row into a remote MySQL table via a PHP script invoked from an AsyncTask in my app. Upon returning from the database, if the insert worked (it could fail if it was a duplicate record), I add the information to my ArrayList and notifyItemInserted in the onPostExecute() method. That all seems to work okay although I always get two copies of the new sale in the RecyclerView. The next time the screen refreshes though - when I do another Add, Update or Remove - the duplicate sale disappears.
Each item in my RecyclerView contains all of the information for one sale, plus two clickable images, one intended for Editing (it's a blue pencil), and one intended for Remove (it's a red garbage can). If the user wants to edit that sale, he clicks on the blue pencil; if he wants to delete it, he clicks on the red garbage can. I use listeners to determine which image got clicked.
When someone clicks on the Edit graphic, I launch an Edit activity, passing the information from the existing sale to that activity. The activity displays the information and the user can modify any of the five fields. All changes are validated and, if all the validations are successful, the changed information is passed back to the Main activity which updates the existing row in the database in the doInBackground() method of an AsyncTask. If the update is successful, I try to change the information in my ArrayList and notifyItemChanged in onPostExecute(). That always fails.
When someone clicks on the Remove graphic, I launch a Remove activity, passing the information from the existing sale to that activity. The activity displays the information and the user can only press Remove to confirm that the sale should be removed or press Cancel to abort the removal. If Remove was pressed, the information from the sale is passed back to MainActivity which removes the existing row in the database in the doInBackground() method of an AsyncTask. If the database delete is successful, I try to delete the item from the ArrayList and then notifyItemRemoved in onPostExecute(). That always fails.
As you can see, the Edit and Remove both fail every time. The reason is that there is an indexOutOfBoundsException with respect to the position used in attempting to modify or remove the item from the ArrayList.
Now that I've set out an understanding of the situation, I can ask my specific questions:
Is it reasonable/appropriate to use Activities to do the work I've described for Add, Edit and Remove processes in the first place or would I be better making them fragments?
Is it appropriate to be doing the database activity and the adjustment of the ArrayList in the main activity or should I be letting the Add, Edit, and Remove activities (or fragments if that's better) do the work?
I think my fundamental problem is that my code can't "see" everything it needs to see at the point where it is doing its work. I think I need to redesign things a bit to make the app work better.
RecyclerView seems to be pretty fundamental so I want to make sure I write everything correctly. In fact, given the absence of good examples that show a RecyclerView that has all the functionality I'm describing, I'd like to write a tutorial or make a video series showing my finished app with all the critical parts explained so that others can learn from it.
A strange behavior I'm tearing my eyes on since early this afternoon, I'm givin up understanding but perhaps someone has an idea (yeah, I'm a beginner, some PROBABLY has an idea ^^).
Situation :
MainActivity.java (first one to be called, only one of interest here)
public ExpandableListView listClubs;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listClubs = (ExpandableListView) findViewById(R.id.expLstMainClubs);
AppGlobal.CreateDistricts() ;
ELVAdapterDistrictsClubs adapter = new ELVAdapterDistrictsClubs(this);
listClubs.setAdapter(adapter);
}
it's the only code in the class. Basically, I'm filling an ExpandableList with an adapter that relies on what's created in the CreateDistricts() method (creates business objects, Districts containing Clubs containing Members, Events, etc.).
Basically (again ^^), everything runs fine, on first start the ExpList works as expected, as well as the rest of the app. If I hit the back button from the MainActivity, however, and rerun the app, a "strange" (to me at least) behavior occurs :
The ExpList is loaded twice in a row - first one with the data, works fine, and just below a duplicate (with the same 2 Groups, i.e. here the only two Districts in my sample data), which doesn't work at all (if you try to expand it, it crashes the app).
Frankly, I'm lost - I've tried some things on the various on[Pause/Stop/Destroy/Start/Resume] etc. to no avail (therefore, there is yet nothing done on this side, no override, as it seems not to bring anything good).
As the onCreate will after the onDestroy load the bundle, it should load the ExpList as it were, and in this case there might be a conflict between the "old" groups/children and the actual ones (currently, data is simulated, but will afterward come from a remote data source), and I haven't decided yet what the best "save" behavior is, i.e. if I should find a way to obliterate the ExpList onDestroy in order to be sure it's 100% recreated anew (and how does one do that ?) and start from scratch, or if I could use the ExpList as is, while updating it's content, in order not to lose the previous selection. It may be irrelevant to complicate matters in order to do that, the first list being not so big and quite quickly used to reach the second activity...
Anyway, I suspect it comes from around that part, the bundle load after destroy and rerunning, does it reload the ExpList as is and then I add things instead of first deleting ? What I found confusing is that the next activity (reached simply by clicking one of the items of the ExpList) displays a similar ExpList (filled with Months and Events per Month for the selected Club), and doesn't present the same strange behavior after a destroy... and both have an almost identical ExpList declaration in their original xml layout so... well, perhaps tomorrow morning I'll see the light, but if anyone has an idea, feel free :D
The more I write, the more I think I should first clear the ExpList but... I don't see how exactly. I've tried
listClubs.removeViewsInLayout(0, listClubs.getCount()) ;
but it just doesn't seem to do anything.
Thanks in advance
Nothing related to Android behavior - I was recreating everytime the whole set of BO behind if the base ArrayList of Districts was not null... instead of simply getting out of the method, but it raises another question - how was the state of this ArrayList, which is declared in a subclass of Application serving as Application in my manifest ? I'll have to make some tests about that...
I have a general scenario, but couldn't make out how to handle it.
I have a ListView with each item having different clickable views. Of which 1 is add as friend button beside the user name in the item.
So, in the ListViewAdapter's getView() i implemented onClick for this button this way.
onClick(){
//make the button invisible, for UX
//change the value of is_friend = 1, in the Adapter ArrayList, which will be helpful when listView is scrolled and getView is called again, to make it visible or invisible.
//Send the info to the server as an AsyncTask
}
This works fine.
Problem:
instead of clicking the add as friend button, I can click the user name go to his profile and click the add as friend button in that Activity.
Now when i comeback to the ListView, the add as friend button is still visible, because the adapter ArrayList has old data taken from server and i am not even changing them.
I thought of StartActivityForResult when user name is clicked. But i might not comeback to the ListView as soon as i click add as friend, there are lot of other actions on a user's profile.
A familiar case easily handled by many apps. Any idea on how this is done?
Thank You
EDIT:
Though the answers given below work. They are not the best way to do.
So, a better way might be to make a call to a static method of the listViewAdapter to update that value only in its ArrayList??
The comment of gsingh2011 is valid. However, I am guessing that your remark will be something like "I populate the ArrayList by querying the server and I don't want to do that to often".
More generally speaking, the comment of gsingh can be translated to this: you should base your adapter and 'ViewProfileActivity' on the same data. You can do this by sharing the ArrayList between the two (as a static, or a public member or whatever you want). When you then change something from your 'ViewProfileActivity', your ListView will automatically update once you switch back to it (because getView() will be called on its adapter again, which will generate a view based on the new data).
The text above is an answer to your question, but let me just add some other remarks:
you say you are sending info to a server in an asynctask. You should change this to using an (Intent)Service for that. You don't have any guarantees that your data will be fully sent when your Activity is put in the background. If you delegate this server synchronization to a Service, it will make sure your process is kept around long enough for your send to finish.
once you have that service, that would be a nice place to put your "shared data" as discussed above. You could also use a small database for that and let the service only handle the synchronization between your local database and the remote server. Then you have something that is beginning to look like a REST implementation, where your device is caching remote data locally, but in the end is only visualizing the state of the remote server. You can look up an excellent google i/o presentation on dealing with REST servers. This presentation is also nice if you are not interested in the REST part, because (maybe more importantly) it shows you what things you have to take into account when you want to have guarantee of successful persistence of data.
I am trying to figure out how, when a user lands on my activity screen, it is "reloaded" as if it were being loaded for the first time.
I don't want my user hitting the back arrow and coming back to my activity with old information.
As it is now, when a user "comes back" to my page, the database list isn't being repopulated, and information they typed into EditText fields remains there.
I want the page, everytime the user comes to it, to be like it's their first time there.
Have you ever tried using recreate() in your current activity? Try using it after your new values are populated.
Or you can just put everything that's in the onCreate() on the onResume. A bit ugly but it works.
I have to disagree with Kartik on this, as I understand, android:noHistory='true' will remove activity from application stack. So when user hits back, user will not be able to see the activity at all.
About activity not retaining its value, I would not recommend you this, as user expects that all values would be retained when back is hit, unless there is some specific requirement that you are trying to meet.
So I guess solution to your problem is, as others have suggested do your initialization on views in onResume(). But just doing this may not be sufficient, as views like EditText will by default cache the values anyways. So you might have to manually clear those in your on onResume(). Will keep looking to find any 'perfect' solution if any to this problem.
I had solving similar problem like yours and I solve it whit lunch mode.
I think the best is to take a look of this set the lunch mode in single instance and then
in you onCreate and onResume you can code to refresh you view just the way you want it.
copy requred code into onResume() from onCreate() method