can't understand how to refresh/update all my textviews. They are located in 13 different fragments and I can't acces them how I want.
In simple Java I got a simple gameloop
//Game Loop
boolean GameLoop = true;
while(GameLoop){
CG.refresh();
}
and In main class I got something like this :
void refresh() {
Labels.MoneyLabel.setText("Money: " + CarMain.main[0]);
Labels.BoxesLabel.setText("Boxes: " + CarMain.main[2]);
}
And It All worked. Now in Android I can't update my textviews.
On Android, onCreate method made simple game loop
Thread t = new Thread() {
#Override
public void run() {
try {
while (!isInterrupted()) {
Thread.sleep(1000);
runOnUiThread(new Runnable() {
#Override
public void run() {
Collections();
SaveFile();
Refresh();
}
});
}
} catch (InterruptedException e) {
}
}
};
t.start();
and wanted to refresh textviews like
public void Refresh(){
TextView MoneyTXT = (TextView) rootView.findViewById(R.id.MoneyText);
MoneyTXT.setText("Money: " + Main.Money[0]);
TextView MoneyPerTapTXT = (TextView) rootView.findViewById(R.id.MoneyPerTapTView);
MoneyPerTapTXT.setText("$ " + Main.Money[1] + " per tap");
TextView BoxesTXT = (TextView) rootView.findViewById(R.id.BoxesText);
BoxesTXT.setText("Boxes: " + Main.Boxes[0]);
TextView BoxesPerTapTXT = (TextView) rootView.findViewById(R.id.BoxesPerTapTView);
BoxesPerTapTXT.setText("Points " + Main.Boxes[1] + " per tap");
}
But got error, becouse Textviews are located in different layouts.
To update a Fragment from an Activity:
Assuming your Fragment has been added to a view with the id R.id.frame, you would do:
// Get current Fragment
Fragment fragment = getFragmentManager().findFragmentById(R.id.frame);
if (fragment instanceof MyFragment) { //or whatever your Fragment's name is
((MyFragment) fragment).updateTextView(); // This is a method you will have to define yourself
}
Does that make sense?
If you do not want to handle via method callback mechanism, you can try the event callback mechanism by using third party libraries like GreenRobot EventBus.
Simple example,
Define events:
public static class MessageEvent { /* Additional fields if needed */ }
Prepare subscribers:(Subscribe your activity to receive events)
Declare and annotate your subscribing method, optionally specify a thread mode:
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
Register and unregister your subscriber. For example on Android, activities and fragments should usually register according to their life cycle:
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
Post events:(On click of the fragment button post the event, activity will be notified immediately)
EventBus.getDefault().post(new MessageEvent());
Related
I start a Thread in a Fragment and use an Interface call, to #Override a method in the Fragment starting the thread. This #Override stops a ProgressDialog and changes the Text of a TextView in the Fragment.
When I do the same in an Activity, there is no Problem but now when using a Fragment I got the "only Thread that creates a View can touch it's views" - Error. So I used getActivity().runOnUiThread(runnable) and posted the code to the MainThread, but why do I need to do this, since it works in a Activity without this? Did I made a mistake?
The Thread
//interface
private ConnectToDevice connectToDevice;
//C-Tor
public Thread_ConnectToDevice(BluetoothDevice device, ConnectToDevice connectToDevice ) {
this.mBluetoothDevice = device;
this.connectToDevice = connectToDevice;
}
//call
connectToDevice.connectionSuccess(false, null);
Fragment
//make Thread
thread_connectToDevice = new Thread_ConnectToDevice(mBluetoothDevice, Fragment_RoutineStartConnection_setPassword.this);
thread_connectToDevice.start();
//CallBack
//Thread Connect Success
#Override
public void connectionSuccess(final Boolean bSuccess,final BluetoothSocket mSocket) {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
if(bSuccess){
mProgessDialog.setTitle(R.string.tv_Fragmentsetpassword_Connected_CheckPW);
if(thread_connectedToDevice != null){
if(thread_connectedToDevice.isAlive()){
thread_connectedToDevice.interrupt();
}
}
thread_connectedToDevice = new Thread_ConnectedToDevice(mSocket, sTryingDonglePassword);
thread_connectedToDevice.start();
}else{
mProgessDialog.dismiss();
tv_Fragmentsetpassword_userhint
.setTextColor(getResources().getColor(R.color.Mercedes_RED, null));
tv_Fragmentsetpassword_userhint.setText(R.string.tv_Fragmentsetpassword_ConnectionFailed);
}
}
});
}
I have the feeling that I passed the wrong listener Instance to the Thread.
As asked this is the callback realized the same way but in a Activity:
Thread
//listener
private Finished_AskingForInformation listener;
//C-Tor
public Td_AskForInformation(
Finished_AskingForInformation listener) {
this.listener = listener;
}
//call
listener.AskingFinished();
Activity
//Create and start thread
td_askForInformation = new Td_AskForInformation(this);
td_askForInformation.start();
//CallBack
#Override
public void AskingFinished() {
mProgressDialog.dismiss();
}
I'm creating a chat app in which I want to upload files with progress showing but could not able to update UI as upload progress.
I created an interface ProgressUpdateListener in the class MultipartUtility used to upload file and creating a object of MultipartUtility and progress listener in which I call PublishProgress to update progress for AsyncTask.
Created one interface in Activity and used to update progress in adapter
/* to get progress update from MultiUtility class using interface in actviity */
MultipartUtility multipart = new MultipartUtility(strings[0], charset, new MultipartUtility.ProgressUpdateListener() {
#Override
public void onProgressUpdate(final long progress, final String msg_id) {
//Toast.makeText(ChatRoom.this, progress+"", Toast.LENGTH_SHORT).show();
Log.e("DataP", progress + "");
publishProgress(String.valueOf(progress), msg_id);
runOnUiThread(new Runnable() {
#Override
public void run() {
mOnDataChangeListener.onDataChanged(progress, msg_id);//activity interface function
dataBaseHelper.updateProgress(msg_id, (int) progress);
chatAdapter.notifyDataSetChanged();
}
});
}
});
/* to get update in adapter from activity using interface function of activity */
activity.setOnDataChangeListener(new ChatRoom.OnDataChangeListener() {
#Override
public void onDataChanged(long progress, String msg_id) {
try {
Log.e("ProgressInAdapter", progress + " ****** " + msg_id);
if (object.getId().equalsIgnoreCase(msg_id)) {
object.setProgress((int) (progress * 10));
dataSet.set(listPosition, object);
((MyVideoHolder)holder).pbProgress.setProgress(object.getProgress());
}
} catch (NullPointerException e) {
e.printStackTrace();
}
}
});
You have to store Viewholder position and progress on change, use this position while update progress bar.
If ViewHolder.getAdapterPosition() == position => Then we are at right viewholder, update UI, otherwise do nothing
You have to initialize listener inside onBindViewHolder and pass to Asynctask to update correct ViewHolder
public void onBindViewHolder(#NonNull MyViewHolder viewHolder, int position) {
// listener passed to asynctask to call on progress change
viewHolder.update( new OnProgressUpdate() {
#Override
public void onProgress(DownloadUpdate update1,String msg1) {
// check whether listener called for right position of item list
if(update1.getPosition() == RecyclerView.NO_POSITION ||
update1.getPosition()!= viewHolder.getAdapterPosition()) return;
viewHolder.bar.setProgress(update1.getProgress());
viewHolder.bar.setMax(update1.getMaxProgress());
viewHolder.msg.setText(msg1);
}
};);
}
You have to maintain state outside list because ViewHolder are re-used, order of viewholder is not sure to store state.
I have implemented and tested with custom class to store progress and implementation here
I am using a RecyclerView that show results that come from GCM callbacks. The RecyclerView has a custom adapter a method add, there is also a progress bar that updates using an asynctask.
Message recieving over GCM that works fine:
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
runOnUiThread(new Runnable() {
#Override
public void run() {
mAdapter.add(new ResultRecord("asf", 89, 1000));
}
});
}
};
Add method in the custom adapter:
public void add(final ResultRecord result) {
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
results.add(0, result);
notifyItemInserted(0);
}
});
}
The problem is that the method add called and nothing happens on the UI. The method add called and then onBindViewHolder and the recycler view does not update. Only when the progress bar is finished the RecylcerView is getting update with all the ViewHolders that has been added before.
I have checked if the add method works from the onCreate method and it worked fine. Maybe this problem is related to threading.
You have a Threading problem here.
Your code is based on ArrayList, which isn't Thread-Safe. You are calling the "Add" method from event, which called probably from multiple threads.
You have to synchronize your code. Something like this:
private final ReentrantLock lock = new ReentrantLock();
public void add(final ResultRecord result) {
lock.lock();
try {
AddNotThreadSafe(result); // Only one thread add in same time. Now is safe for executing.
} finally {
lock.unlock();
}
}
Now, move your original Add code to separated method called AddNotThreadSafe.
This should work. :)
My problem is pretty simple. I am creating a card based on the result of a HTTP query performed inside a separate thread. The card also has an onclick method and is defined inside a runOnUiThread() located inside the separate thread. However, when the device is tapped, the onclick event isn't fired.
Here is my code:
private void login() {
Runnable r = new Runnable() {
#Override
public void run() {
// irrelevant code
runOnUiThread(new Runnable() {
#Override
public void run() {
setContentView(buildError(code));
}
}
}
Thread t = new Thread(r);
t.start();
}
private View buildError(String code) {
CardBuilder card = new CardBuilder(this, CardBuilder.Layout.ALERT);
card.setIcon(R.drawable.ic_warning_150);
if (code.equals("1"))
card.setText("Incorrect credientals");
else
card.setText("Unexpected error");
card.setFootnote("Tap to try again");
View cView = card.getView();
cView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.i("Event", "Clicked"); // This is what isn't triggering
}
});
cView.setFocusable(true);
cView.setFocusableInTouchMode(true);
return cView;
}
Even though the snippet of code contains an error (can't be compiled, missing ; at the Runnable statement), you were on the right track.
The View simply needs to request the focus in order to be clickable right away. Otherwise you'll have to move the focus manually.
cView.setFocusable(true);
cView.setFocusableInTouchMode(true);
cView.requestFocus();
Reference
A button triggers an action that should only be invoked once. The button is disabled and hidden in the onClick handler before the action is performed:
someButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
someButton.setEnabled(false);
someButton.setClickable(false);
someButton.setVisibility(View.GONE);
performTaskOnce();
}
});
private void performTaskOnce() {
Log.i("myapp", "Performing task");
//Do something nontrivial that takes a few ms (like changing the view hierarchy)
}
Even though the button is disabled immediately, it is nonetheless possible to trigger multiple "onClick" events by tapping multiple times very quickly. (i.e. performTaskOnce is called multiple times). Is seems that the onClick events are queued before the the button is actually disabled.
I could fix the problem by checking in every single onClick handle whether the corresponding button is already disabled but that seems like a hack. Is there any better way to avoid this issue?
The problem occurs on Android 2.3.6, I cannot reproduce it on Android 4.0.3. But given the rarity of 4.x devices it is not an option to exclude older devices.
You could set a boolean variable to true when the button is clicked and set it to false when you're done processing the click.
This way you can ignore multiple clicks and not having to disable the button possibly avoiding annoying flickering of the button.
boolean processClick=true;
someButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if(processClick)
{
someButton.setEnabled(false);
someButton.setClickable(false);
someButton.setVisibility(View.GONE);
performTaskOnce();
}
processClick=false;
}
});
private void performTaskOnce() {
Log.i("myapp", "Performing task");
//Do something nontrivial that takes a few ms (like changing the view hierarchy)
}
In the interest of keeping DRY:
// Implementation
public abstract class OneShotClickListener implements View.OnClickListener {
private boolean hasClicked;
#Override public final void onClick(View v) {
if (!hasClicked) {
onClicked(v);
hasClicked = true;
}
}
public abstract void onClicked(View v);
}
// Usage example
public class MyActivity extends Activity {
private View myView;
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myView.setOnClickListener(new OneShotClickListener() {
#Override public void onClicked(View v) {
// do clicky stuff
}
});
}
}
Bit late but this might be of use to someone. In my case I am calling another activity so;
Declare a boolean;
boolean clickable;
In the click listener;
if(clickable){
// Launch other activity
clickable = false;
}
Enable when onResume is called;
#Override
public void onResume() {
Log.e(TAG, "onResume");
super.onResume();
clickable = true;
}
You can use RxView(com.jakewharton.rxbinding2.view.RxView) is an extension around RxJava that created by Jake Wharton.
To integrate it to project you should use implementation 'com.jakewharton.rxbinding3:rxbinding:3.1.0'
Simple Java usage:
RxView.clicks(yourButton)
.sample(500, TimeUnit.MILLISECONDS)
.subscribe { action() }
In Kotlin you can create extension function to handle your clicks:
View.singleClick(action: () -> Any) {
RxView.clicks(this)
.sample(500, TimeUnit.MILLISECONDS)
.subscribe { action() }
}
Sample:
Kotlin
yourButton.singleClick({
//do some stuff here
})
Java
SingleClickListenerKt.singleClick(yourButton, () -> {
doSomeStuff();
return null;
});
Note: you can use any RxJava operators like debounce, map, first, etc if you wish.
declare a varieble
and use it as
boolean boo = false;
someButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if(boo==false){
someButton.setEnabled(false);
someButton.setClickable(false);
someButton.setVisibility(View.GONE);
boo = true;
}
}
});
by this you prevent multiple clicks on your button
hope it help