I have implemented dynamic tab navigation with fragments. Fragments are stored on the stack and when user change tab, new fragment should replace existing content to show new items.
But first, data must be refreshed (they will be downloading from rest webservice) and it takese some time, so there is also BusyFragment, indicating data loading process.
I implemented background refreshing data with AsyncTask and I have separate AsyncTask object that is called to refresh specific tabs, this way:
private void RefreshShopsMainMenuFragment(Fragment f)
{
if (refreshAsyncTaskShops != null && refreshAsyncTaskShops.getStatus() != AsyncTask.Status.FINISHED)
return;
final ListFragment fragmentToRefresh = (ListFragment) f;
refreshAsyncTaskShops = new AsyncTask<Void, Void, ArrayList<ListDataDef>>()
{
#Override
protected void onPreExecute()
{
ShowBusyFragment(true, Global.TAB_SHOPS); //for other tabs, other tab identifiers from Global
}
#Override
protected ArrayList<ListDataDef> doInBackground(Void... voids)
{
return ShopsTabDataProvider.GetNewData(); //For other tabs, different data provider call
}
#Override
protected void onPostExecute(ArrayList<ListDataDef> result)
{
fragmentToRefresh.mainData = result;
fragmentToRefresh.DisplayNewData();
ShowBusyFragment(false, Global.TAB_SHOPS); //for other tabs, different tab indentifier
}
};
refreshAsyncTaskShops.execute();
}
So, basically as shown above: to refresh shops tab, refreshAsyncTaskShops will be called, there are also other async task object, I marked lines where they are different.
Is there any more elegant solution, without declaring multiple AsyncTask objects? I'm not fully satisfied with my current implementation, because my all async tasks objects are basically the same.
Also, as you can see, there is no any queue so don't need to wait for first tab refreshing and to refresh another one. However, refreshing the same tab multiple times simultaneously should never happen so I added if condition to prevent the same task to be executed if previous one is not finished yet.
Related
Case is after I got a response from my async task, I want to show a DialogFragment.
But if user put the application to background while the app still waiting for the response, on the moment the response came and .show DialogFragment it will crashed.
I've done immediate fix by try catching the .show, but the DialogFragment won't show after user return to the app.
Is there a clean way to let the application keep on showing DialogFragment while on background or on the next onResume ?
The only way I found while googling is using an ActivityDialog, but it will require much effort.
Edit : Eh I actually able to show it now with commitStateLoss ._.
from
customErrorDialog.show(((FragmentActivity) context).getSupportFragmentManager(), "TAG");
to
((FragmentActivity)context).getSupportFragmentManager().beginTransaction().add(customErrorDialog, "TAG").commitAllowingStateLoss();
idk if this is dangerous for some specific case though
You should use lifecycle-aware components to receive response.
For android-java projects simply use livedata. Base on the document:
LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state. LiveData considers an observer, which is represented by the Observer class, to be in an active state if its lifecycle is in the STARTED or RESUMED state.
For android-kotlin projects, you have more options than java. You can still use livedata like Java. Other options are StateFlow, which is part of the flow and coroutines. Collecting them with repeatOnLifecycle. Base on document:
public class NameActivity extends AppCompatActivity {
private NameViewModel model;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Other code to setup the activity...
// Get the ViewModel.
model = new ViewModelProvider(this).get(NameViewModel.class);
// Create the observer which updates the UI.
final Observer<String> nameObserver = new Observer<String>() {
#Override
public void onChanged(#Nullable final String newName) {
// Update the UI, in this case, a TextView.
nameTextView.setText(newName);
}
};
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
model.getCurrentName().observe(this, nameObserver);
}
}
StateFlow and SharedFlow are Flow APIs that enable flows to optimally emit state updates and emit values to multiple consumers.
You can find this behavior in other observable classes like LiveData
class LatestNewsActivity : AppCompatActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
...
// Start a coroutine in the lifecycle scope
lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Trigger the flow and start listening for values.
// Note that this happens when lifecycle is STARTED and stops
// collecting when the lifecycle is STOPPED
latestNewsViewModel.uiState.collect { uiState ->
// New value received
when (uiState) {
is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
is LatestNewsUiState.Error -> showError(uiState.exception)
}
}
}
}
}
We are using RealChangeListener to listen data changes and updating listview by calling notifydatasetgchanged().
Initial sync time we do get many records from server(per batch 100 records), loop thru results updating Realm like below in background thread.
for(int i=0;i<results.size();i++)
{
// processing and validation
....
db.beginTransaction();
db.copyToRealm(processedObject);
db.commitTransaction();
}
In activity, we are registered realmResults change listener like below code
#Override
public void onStart() {
super.onStart();
mEntityDataProvider = new EntityDataProvider();
mEntityDataProvider = mEntityDataProvider.getListAsync();
mEntityDataProvider.addChangeListener(realmEntityChangeListener);
}
private RealmChangeListener<RealmResults<Entity>> realmEntityChangeListener = new RealmChangeListener<RealmResults<Entity>>() {
#Override
public void onChange(RealmResults<Entity> realmResults) {
if (mEntityListAdapter!= null) {
mEntityListAdapter.setData(realmResults);
mEntityListAdapter.notifyDataSetChanged();
}
}
};
Questions:
Is it best practice to call notifyDataSetChanged() in
reamlChangeListner?
For every commitTransaction() i think
realmChangeListener will be called, calling notifyDataSetChanged() calling many times is it fine?
If above practice is not good to do,
suggest me if any alternatives I need to consider.
Thanks
Yes
I think you can put the loop between beginTransaction()/commitTransaction() to avoid refresh the UI too many times.
See https://realm.io/docs/java/latest/#adapters, You can just use the adapters from Realm. And in the mEntityListAdapter.setData(realmResults); doesn't seem to be necessary to be called every time if the it is on the same RealmResults.
I have three java files in my Android project. Two are activities (MainActivity and GeoActivity) and one is a plain java file (PostHttp -> sends data to server via the HTTP POST)
I switch over to GeoActivity via a simple button on-click method. GeoActivity returns the co-ordinates of the current location in a TextView AND sends them to a remote server via the HTTP POST.
I have a Handler.class which executes sends the Post Message after a delay of 50s. Something like this below. The problem i have is that when i click the back button and switch over to MainActivity i can still see in LogCat the echoes receiving from the server that the data is still being sent. How can i stop that?
GeoActivity.class
public class GeoActivity extends Activity {
Location location;
private Handler mHandler = new Handler();
protected void onCreate(Bundle savedInstanceState) {
....
if(location != null){
mHandler.postDelayed(updateTask,0);
}
...
}
...
public Runnable updateTask = new Runnable(){
public void run(){
mlocListener.onLocationChanged(location);
//send coordinates with a delay of 50s
new PostHttp(getUDID(),latitude,longitude).execute();
mHandler.postDelayed(updateTask, 50000);
}
Try acting on the activity's life cycle.
For example:
#Override
protected void onStop() {
super.onStop(); // Always call the superclass method first
// Save the note's current draft, because the activity is stopping
// and we want to be sure the current note progress isn't lost.
ContentValues values = new ContentValues();
values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());
getContentResolver().update(
mUri, // The URI for the note to update.
values, // The map of column names and new values to apply to them.
null, // No SELECT criteria are used.
null // No WHERE columns are used.
);
}
This doesn't destroy the activity, it will reside in memory. However, you can always resume when needed.
Source:
Stopping and Restarting Android Activities
my goal is to insert to a certain db 2 values, id and pass.
I have a registeration page which asks for that data and a button to complete the action.
So on the button listener what should I do?many told me to use AsyncTask (which I don't know to use) instead of Thread.
Remember that this class needs to get 2 parameters id and pass .. and as far as I know threads starts after using the start() method which invoke the run method, and the run method has no parameters.. so how can I pass those 2 parameters?
Anyway I'm very confused.
Another thing is that if I get any kind of error on the catch block I will put the error on a certain string something like : String error = exceptionInstance.toString(); and then I can take see that string from the registeration page and print the error.
myThreadInstance.start();
textViewInstance.setText(myThreadInstance.getError());
It's some kind of a marathon.. I'M CONFUSED!!!!!!!
According to me use AsyncTask instead of an Thread because it's easy to use and you have better control on Background thread without doing extra code for creating separate logic for updating Ui when Thread execution complete, calculate progress units to so user how much time take by an operation to done etc
Your First question how you send username and password to AsyncTask on button click .for this use AsyncTask Constructor as:
LoginOperation loginopertion=new LoginOperation(strusername, strpassword);
loginopertion.execute("");
Your Second answer how we receive username and password in AsyncTask and update Ui when Task complete for this use onPostExecute of AsyncTask to update Ui when doInBackground execution complete for example :
public class LoginOperation extends AsyncTask<String, Void, String> {
String strusername,strpassword;
public LoginOperation(String strusername, String strpassword){
this.strusername=strusername;
this.strpassword=strpassword;
}
#Override
protected void onPreExecute() {
//show progressbar here
}
#Override
protected String doInBackground(String... params) {
string result="";
try
{
result=="success or fail";
//do your network opertion here
}
catch(SQLException e)
{
result="ERROR";
}
return result;
}
#Override
protected void onPostExecute(String resultmsg) {
// show error here and update UI
//or other opertion if login success
textViewInstance.setText(resultmsg);
}
}
For more information about AsyncTask method's see
http://developer.android.com/reference/android/os/AsyncTask.html
I have an onCreate method that runs the code below. In a nutshell the code retrieves data from the server and shows it on the screen for a messaging program. It only does it once, but I would like it to run the AsyncTask every 3 seconds (to try to simulate a chat). I'm pretty sure this is not the way to go about having a chat system but, I just need something that works for now (as a proof of concept) and I'll focus on the correct way of implementing it later.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chat_box);// sd
final Functions function = new Functions();
final SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(getBaseContext());
whatroom = prefs.getString("chat", "null");
new AsyncTask<String, Void, String>() {
#Override
protected String doInBackground(String... args) {
return function.getInbox(args[0]);
}
#Override
protected void onPostExecute(String result) {
TextView inbox = (TextView) findViewById(R.id.inbox);
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar1);
progressBar.setVisibility(View.GONE);
inbox.setText(result);
}
}.execute(whatroom);
}
I've tried putting a simple while statement around the asynctask but, it just force closes.
You cannot reuse an AsyncTask instance. You would need to create fresh instances each pass of your loop.
Without additional information, it's difficult to give you a specific answer. However look into abstracting everything using a Loader, using a Service, etc
Regarding Loaders:
They are available to every Activity and Fragment.
They provide asynchronous loading of data.
They monitor the source of their data and deliver new results when the content changes.
They automatically reconnect to the last loader's cursor when being recreated after a configuration change. Thus, they don't need to re-query their data.