Prevent multiple touches after touching an item on the list - android

I have a main activity with a list view, an onItemClickListener() event is associated to the list view and when I touch an element I start an AsyncTask that does some work. I would like to disable the other touches that the user could make to the other elements of the list during the async task execution and re-enable them when the async task ends.
How could I do?
This is my activity
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener{
private ListView newList;
private ArrayAdapter<String> newArrayAdapter;
private MyAsyncTask asyncTask;
private ProgressBar progressBar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.progressbar);
newArrayAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1);
newList = findViewById(R.id.pairedDevicesListView);
newList.setOnItemClickListener(this);
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
newList.setOnItemClickListener(null); //need something like this
asyncTask = new MyAsyncTask(this);
asyncTask.execute();
}
#Override
public void onBackPressed() {
asyncTask.cancel(true);
super.onBackPressed();
}
private class MyAsyncTask extends AsyncTask<Void,Void,Void>{
Context context;
public MyAsyncTask(Context context){
this.context = context.getApplicationContext();
}
#Override
protected Void doInBackground(Void... voids) {
// do some stuff
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
progressBar.setVisibility(View.GONE);
newList.setOnItemClickListener();
//does not work or I don't know what to pass
}
}
}

Try this:
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (asyncTask == null || asyncTask.getStatus() == AsyncTask.Status.FINISHED) {
asyncTask = new MyAsyncTask(this);
asyncTask.execute();
}
}
Be careful with onBackPressed, or you may call a function on a null object.
#Override
public void onBackPressed() {
if (asyncTask != null) {
asyncTask.cancel(true);
}
super.onBackPressed();
}

You could write something like this in your onItemClickListener:
newList.setEnabled(false);
And then in your onPostExecute method write:
newList.setEnabled(true);

Related

Toast in OnPostExecute() once the fragment changes

I have two fragments in my Activity : Fragment_A and Fragment_B.
In Fragment A, I created an AsyncTask (when the user "swipeRefreshes" the screen). In the onPostExecute() of this task, I want to display a Toast :
private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
private Exception mLastError = null;
MakeRequestTask() {
//Some stuff
}
#Override
protected List<String> doInBackground(Void... params) {
//Some stuff
}
#Override
protected void onPreExecute() {
//Some stuff
}
#Override
protected void onPostExecute(List<String> output) {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
}
#Override
protected void onCancelled() {
swipeRefreshLayout.setRefreshing(false);
//Some stuff
}
}
If the user changes from Fragment_A to Fragment_B before the AsyncTask finishes, I get a crash:
java.lang.IllegalStateException: Fragment Fragment_A not attached to a context.
I know how to avoid the crash (by adding the condition isAdded()), but I want my Toast to be displayed no matter which Fragment is displayed/alive on top of my Activity.
1stly I would like to suggest you, please make your MakeRequestTask inner class as static as this can be a memory leak.
For your question, You need to pass the context to the class like below:
private static class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
private Exception mLastError = null;
private WeakReference<Context> weakReference;
MakeRequestTask(Context context) {
//Some stuff
weakReference = new WeakReference<>(context);
}
#Override
protected List<String> doInBackground(Void... params) {
//Some stuff
}
#Override
protected void onPreExecute() {
//Some stuff
}
#Override
protected void onPostExecute(List<String> output) {
// swipe layout will not be shown if fragment is not visible or destroyed
if (isFragmentVisible) {
swipeRefreshLayout.setRefreshing(false);
}
// toast will be shown no matter what fragment is visible
if (weakReference != null) {
Context context = weakReference.get();
if (context != null) {
Toast.makeText(context, "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
}
}
#Override
protected void onCancelled() {
if (isFragmentVisible) {
swipeRefreshLayout.setRefreshing(false);
}
//Some stuff
}
}
Try this way
Declare a boolean in Fragment_A
private boolean isFragmentVisible=false;
In Fragment_A class
Make this boolean true in onCreateView() of this Fragment_A
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
view = inflater.inflate(R.layout.lyourlayout, container, false);
isFragmentVisible = true;
return view;
}
And make this boolean false in onDestroyView() of this fragment A
#Override
public void onDestroyView() {
super.onDestroyView();
isFragmentVisible = false;
}
Finally use it in Asyntask of Fragment_A like this
private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
private Exception mLastError = null;
MakeRequestTask() {
//Some stuff
}
#Override
protected List<String> doInBackground(Void... params) {
//Some stuff
}
#Override
protected void onPreExecute() {
//Some stuff
}
#Override
protected void onPostExecute(List<String> output) {
// swipe layout will not be shown if fragment is not visible or destroyed
if(isFragmentVisible){
swipeRefreshLayout.setRefreshing(false);
}
// toast will be shown no matter what fragment is visible
Toast.makeText(getActivity(), "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
#Override
protected void onCancelled() {
if(isFragmentVisible){
swipeRefreshLayout.setRefreshing(false);
}
//Some stuff
}
}
Or you can just use and interface or an EventBus in the onPostExecute method, and show the Toast inside the activity.
#Override
protected void onPostExecute(List<String> output) {
swipeRefreshLayout.setRefreshing(false);
activityContractInterface.showToast()
}
}
And in your Activity:
#Override
public void showToast(){
Toast.makeText(getActivity(), "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
Or the EventBus approach:
#Override
protected void onPostExecute(List<String> output) {
swipeRefreshLayout.setRefreshing(false);
EventBus.getDefault().post(new ShowToastEvent())
//just create an empty class, hope you know what EventBus is
}
And in your activity:
#Subscribe(threadMode = ThreadMode.Main){
Toast.makeText(getActivity(), "TO_DISPLAY", Toast.LENGTH_SHORT).show();
}
EventBusLibrary

Only the original thread that created a view hierarchy can touch its views. - Strange behavior

This is a very strange behavior and I don't know how to fix it.
I have an Activity as a Presenter (In a MVP Architecture).
When the activity starts, I attach a Fragment as a View. The fragment itself is very simple.
public class CurrentSaleFragment extends BaseFragment {
private MainMVP.SalesPresenterOps salesPresenterOps;
private SaleAdapter adapter;
private ListView lv;
#BindView(R.id.btn_sell)
FloatingActionButton btnAdd;
public static CurrentSaleFragment newInstance(){
CurrentSaleFragment fragment = new CurrentSaleFragment();
Bundle arguments = new Bundle();
arguments.putInt(LAYOUT_RES_ID, R.layout.fragment_quick_sale );
fragment.setArguments(arguments);
return fragment;
}
#Override
protected void init() {
super.init();
lv = (ListView)view.findViewById(R.id.lv_sale);
}
#OnClick(R.id.btn_sell)
public void addToSale(View view){
mPresenter.moveToFragment(SellProductFragment.newInstance());
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
salesPresenterOps = (MainMVP.SalesPresenterOps)context;
}
#Override
public void onDetach() {
salesPresenterOps = null;
super.onDetach();
}
}
The BaseFragment from which this fragmend extends :
public class BaseFragment extends Fragment implements MainMVP.RequiredViewOps, View.OnClickListener,
LoaderRequiredOps{
protected View view;
protected MainMVP.PresenterOps mPresenter;
protected final static String LAYOUT_RES_ID = "layout_res_id";
#Override
public void showOperationResult(String message, final long rowId) {
Snackbar.make(view, message, Snackbar.LENGTH_LONG).setAction(
R.string.see, new View.OnClickListener() {
#Override
public void onClick(View v) {
onOperationResultClick(rowId);
}
}
).show();
}
#Override
public void showSnackBar(String msg) {
Snackbar.make(view, msg, Snackbar.LENGTH_SHORT).show();
}
#Override
public void showAlert(String msg) {}
protected void onOperationResultClick(long rowId){}
#Override
public void onAttach(Context context) {
super.onAttach(context);
mPresenter = (MainMVP.PresenterOps)context;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
this.view = inflater.inflate(getArguments().getInt(LAYOUT_RES_ID), null);
init();
return view;
}
protected void addToClickListener(View ... params){
for (View v : params){
v.setOnClickListener(this);
}
}
protected void init() {
if (view != null){
ButterKnife.bind(this, view);
}
}
#Override
public void onDetach() {
mPresenter = null;
Log.d(getClass().getSimpleName(), "Fragment was detached");
super.onDetach();
}
#Override
public void onClick(View v) {}
#Override
public void onPreLoad() {
Dialogs.buildLoadingDialog(getContext(), "Loading...").show();
}
#Override
public void onLoad() {}
#Override
public void onDoneLoading() {
Dialogs.dismiss();
}
}
When I enter the method 'moveToFragment()' I just replace CurrentSaleFragment for a new Fragment:
protected void addFragment(BaseFragment fragment){
mView = fragment;
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_holder,
fragment, null).addToBackStack(null).commit();
}
Then the new fragment is attached:
public class SellProductFragment extends BaseFragment{
private ListView listView;
private ProductListAdapter adapter;
private MainMVP.SalesPresenterOps mSalesPresenter;
public static SellProductFragment newInstance(){
SellProductFragment fragment = new SellProductFragment();
Bundle arguments = new Bundle();
arguments.putInt(LAYOUT_RES_ID, R.layout.fragment_inventory);
fragment.setArguments(arguments);
return fragment;
}
private void reload(){
final Loader loader = new Loader(this);
loader.execute();
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
mSalesPresenter = (MainMVP.SalesPresenterOps)context;
}
#Override
protected void init() {
super.init();
listView = (ListView)view.findViewById(R.id.lv_inventory);
reload();
FloatingActionButton button = (FloatingActionButton)view.findViewById(R.id.btn_add);
addToClickListener(button);
}
#Override
public void onLoad() {
adapter = new ProductListAdapter(getActivity().getApplicationContext(), R.layout.row_product_item,
mSalesPresenter.getProducts());
try{
updateListView();
}catch (Exception e){
Log.w(getClass().getSimpleName(), e.getMessage());
}
}
private void updateListView(){
if (adapter != null && listView != null){
listView.setAdapter(adapter);
}else{
throw new RuntimeException();
}
}
}
See that This fragment also extends from BaseFragment and implements LoaderRequiredOps. The interface is used to 'load' any data. It adds a dialog and updated the adapter when the loading is done:
public class Loader extends AsyncTask<Void, Void, Void> {
private LoaderRequiredOps presenter;
public Loader(LoaderRequiredOps presenter){
this.presenter = presenter;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
presenter.onPreLoad();
}
#Override
protected Void doInBackground(Void... params) {
presenter.onLoad();
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
presenter.onDoneLoading();
presenter = null;
}
}
Now, when I try to execute the method reload() from the SellProductFragment i get the 'Only the original thread that created a view hierarchy can touch its views.'
This does not happen if the SellProductFragment is attached first instead of CurrentSaleFragment.
What is happening here?
Your Async Loader class calls the presenters method onLoad() from a background thread during doInBackground().
My guess is that in the onLoad() method of the presenter, a view is referenced.
In order to change the view at this point, post the view logic as a Runnable to the UI thread (you said your presenter is the activity, so this should be possible from the onLoad method).
#Override
public void onLoad() {
runOnUiThread(new Runnable() {
#Override
public void run() {
// Your ui code here...
}
});
// Rest of your code here...
}
For an unknown reason, an unidentified configuration allows to execute the setting of an adapter for a ListView on the doInBackground() method.
Moved it to onPostExecute() and now it's working

Asynctask from Asynctask

My app requires to parse a XML file when the activity is launched. So, I use an Asynctask to parse the XML.
After parsing the XML file, I get the count of number of test-cases in the XML in doInBackground() method, and I use the variable alTestCaseList to keep this count.
Later in onPostExecute() method, I set the ArrayAdapter and register the click listener for the list.
However, when I click any testcase from the list, I'm supposed to parse the test-case entries from the XML again.
So I believe for this I'll have to use an AsyncTask again. So do I start another AsyncTask for onPostExecute() method of first AsyncTask?
Is there any other neat way of doing this?
I tried to put setOnItemClickListener() in onCreate() method, but it resulted in fatal exception with the message: "setOnItemClickListener(android.widget.AdapterView$OnItemClickListener) on a null object reference"........
Kindly give your suggestion.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_testcases);
xmlHelp = new XmlHelper();
ConfigParser confParser = new ConfigParser();
confParser.execute();
}
private class ConfigParser extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
alTestCaseList = xmlHelp.getNumberOfNodes();
return null;
}
#Override
protected void onPostExecute(Void v) {
testCasesListView = (ListView) findViewById(R.id.lstTestCases);
arrayAdapter = new ArrayAdapter(TestCasesActivity.this, android.R.layout.simple_list_item_1, alTestCaseList);
testCasesListView.setAdapter(arrayAdapter);
testCasesListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapter, View v, int position, long arg3) {
String value = (String) adapter.getItemAtPosition(position);
Log.d("QcTool", "Selected: " + value);
}
});
}
}
Here is my take on the solution. Pass your data as an argument to the task and notify the adapter when you get the list.
See the comments for further explanation. Note that this approach does not handle issues that AsyncTask's typically come with is situations such as configuration changes.
You can then create another ParseXmlTask class which can be called in your OnItemClicked method
private ListView testCasesListView;
private ArrayAdapter arrayAdapter;
private List<String> testCasesList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_testcases);
//Init the list - it's empty but your task will fill it.
testCasesList = new ArrayList<>();
//Init your listView
testCasesListView = (ListView) findViewById(R.id.lstTestCases);
//Add adapter to the listView
arrayAdapter = new ArrayAdapter(TestCasesActivity.this, android.R.layout.simple_list_item_1, alTestCaseList);
//Add your click event
testCasesListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapter, View v, int position, long arg3) {
//When clicked, do something awesome
String value = (String) adapter.getItemAtPosition(position);
Log.d("QcTool", "Selected: " + value);
//Create and start parseXmlTask here
}
});
xmlHelp = new XmlHelper();
//Pass in your callback as an argument
ConfigParserTask confParser = new ConfigParserTask(new OnConfigParserTaskCompleted(){
public void onConfigParserTaskCompleted(List<String> result){
//Simply refresh the list
testCasesList.clear();
testCasesList.addAll(result);
//Let the adapter know that the list has changed
//Then update the list view
arrayAdapter.notifyDataSetChanged();
}
});
confParser.execute(xmlHelp);
}
//It's better to pass in the info to the task as arguments than it is to rely on
//field variables
private class ConfigParserTask extends AsyncTask<XmlHelper, Void, List<String>> {
private OnConfigParserTaskCompleted listener;
public ConfigParser(OnConfigParserTaskCompleted listener){
this.listener = listener;
}
#Override
protected Void doInBackground(XmlHelper... params) {
//Do what you need to in the background
//Get your nodes then return it here
List<String> nodes = params[0].getNumberOfNodes();
return nodes;
}
#Override
protected void onPostExecute(List<String> result) {
//pass the result to the callback
listener.onConfigParserTaskCompleted(result);
}
}
//Callback to let your activity/fragment know when
//the task is complete
public interface OnConfigParserTaskCompleted{
public void onConfigParserTaskCompleted(List<String> result);
}
You can do something like this using AsyncTasks
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_testcases);
testCasesListView = (ListView) findViewById(R.id.lstTestCases);
testCasesListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapter, View v, int position, long arg3) {
String value = (String) adapter.getItemAtPosition(position);
Log.d("QcTool", "Selected: " + value);
ParserNodeTask nodeTask = new ParserNodeTask();
nodeTask.execute(value);
}
});
xmlHelp = new XmlHelper();
ParserNumberOfNodesTask numberOfNodesTask = new ParserNumberOfNodesTask();
numberOfNodesTask.execute();
}
private class ParserNumberOfNodesTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
alTestCaseList = xmlHelp.getNumberOfNodes();
return null;
}
#Override
protected void onPostExecute(Void v) {
arrayAdapter = new ArrayAdapter(TestCasesActivity.this, android.R.layout.simple_list_item_1, alTestCaseList);
testCasesListView.setAdapter(arrayAdapter);
}
}
private class ParserNodeTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(String... params) {
String value = params[0];
//TODO : parse the selected node
}
#Override
protected void onPostExecute(Void v) {
//TODO: dunno what you need to do later
}
}
Though AsyncTasks are not ideal for this for many reasons (but easier to implement). You should maybe take a look on to Loaders or Services (i.e https://stackoverflow.com/a/6957909/665823)

Handling async call inside onItemClick()

I have an implementation of onItemClick() while selecting from a list of items. However inside
onItemClick() I need to perform a check via a library call that is async. Basically within onItemClick() a call to XYZ.checkConnection(booleanCallback); is made which is async.
And then in booleanCallback(int status) I can check for the status. What kind of pattern can I use to handle this scenario?
public class Tester extends Activity {
private ListView list;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
list.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
new CheckConnection(list.getAdapter().getItem(position))
.execute();
}
});
}
private class CheckConnection extends AsyncTask<Void, Void, Void> {
private Object o;
public CheckConnection(Object o) {
this.o = o;
}
#Override
protected void onPreExecute() {
// Do things like initiate progress bar etc
}
#Override
protected Void doInBackground(Void... params) {
// Do connection check
return null;
}
#Override
protected void onPostExecute(Void result) {
int status = 0;
// determine status
booleanCallBack(status, o);
}
}
private void booleanCallBack(int status, Object o) {
// perform UI related
}
}
you use the delegate approach if you want a modular class
I have essentially given you a starting point,this can be tweaked based on what you want.I didnt exactly get your context.hence the generalised answer

run onresume() method when i change tab "ontabchange()" in a view

I put the code in onResume() method for it to run each time when i load it again by tab click but problem is now that data load first time from server in to list view when I click first time on tab and when I change the tab and load it again it force close and gives "array index out of bound exception". I think it is because it not removes previous loaded data and so how to remove or reload new data on tab click so that exception not occur? This means before loading new data via onResume() how to delete old data?
protected void onPause() {
super.onPause();
}
protected void onResume()
{
super.onResume();
**new ProgressTask6().execute();**
}
private class ProgressTask6 extends AsyncTask<String, Void, Boolean> {
private ProgressDialog dialog;
private Context context;
#Override
protected void onPreExecute() {
dialog = new ProgressDialog(OpeningToday.this);
dialog.setMessage("Processing...");
dialog.setIndeterminate(true);
dialog.setCancelable(false);
dialog.show();
}
#Override
protected void onPostExecute(final Boolean success) {
if (dialog.isShowing())
{
dialog.dismiss();
setListAdapter(new MyAdapter(OpeningToday.this));
}
}
#Override
protected Boolean doInBackground(String... args) {
try{
} catch (Exception e){
Log.e("tag", "error", e);
return false;
}
return null;
}
class MyAdapter extends BaseAdapter implements OnClickListener
{
}
#Override
public int getCount() {
} }
/* Not implemented but not really needed */
#Override
public Object getItem(int position) {
return null;
}
/* Not implemented but not really needed */
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View ConvertView, ViewGroup parent)
{
View v = inflater.inflate(R.layout.listitem_layout, parent, false);
// Log.i("array galoijewdh..",keywordresulttab.array_galleryname[position]);
Log.i("saurabh trivedi","saurabh trivedui");
// Variables.a=3;
String gallerynames = keywordresulttab.array_galleryname[position];
String addresses = keywordresulttab.array_address[position];
TextView tv = (TextView) v.findViewById(R.id.barrio);
tv.setText(gallerynames);
tv = (TextView) v.findViewById(R.id.ciudad);
tv.setText(addresses);
((BaseAdapter)(getListAdapter())).notifyDataSetChanged();
return v;
}
#Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
}
}
}
Initialize the index / delete data in onPause() which is the opposite of onResume().
As a rule of thumb (according to activity lifecycle) - clean what you need in the opposite method -
onCreate() - onDestroy()
onStart() / onRestart() - onStop()
onResume() - onPause()

Categories

Resources