I am writing a simple stopwatch application for android which will just keep the counter updating each second when pressed start, and pause it with the same button. In my main.xml layout I have a TextView and a Button, and nothing else.
Here's what I've written so far.
public class MainActivity extends Activity implements View.OnClickListener {
private Button btnToggle;
private TextView tvStopwatch;
public Handler updateStopwatch;
private enum Status {
WORKING,
STOPPED
}
private Status stopwatchStatus;
private void toggleStatus() {
if (stopwatchStatus == Status.WORKING) {
stopwatchStatus = Status.STOPPED;
Stopwatch.getStopwatch().dispose();
btnToggle.setText("Start");
}else{
stopwatchStatus = Status.WORKING;
Stopwatch.getStopwatch().start();
btnToggle.setText("Stop");
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initView();
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnToggle:
toggleStatus();
break;
}
}
private void initView() {
btnToggle = (Button) findViewById(R.id.btnToggle);
tvStopwatch = (TextView) findViewById(R.id.tvStopwatch);
updateStopwatch = new Handler(){
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
tvStopwatch.setText(msg.what);
}
};
Stopwatch.getStopwatch().setHandler(updateStopwatch);
btnToggle.setOnClickListener(this);
stopwatchStatus = Status.STOPPED;
}
public static class Stopwatch {
private int timestamp;
private boolean working;
private Handler updateStopwatch;
private Stopwatch() {
}
private static Stopwatch stopwatch;
public static Stopwatch getStopwatch() {
if (stopwatch == null) {
stopwatch = new Stopwatch();
}
return stopwatch;
}
public void setHandler(Handler updateStopwatch){
this.updateStopwatch = updateStopwatch;
}
public void start() {
if (!working) {
working = true;
Thread tick = new Thread() {
public void run() {
while (working) {
try {
sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
} finally {
timestamp++;
updateStopwatch.sendEmptyMessage(timestamp);
}
}
}
};
tick.start();
}
}
public void dispose() {
working = false;
}
}
}
The point is, I want my Stopwatch work in it's separate thread, and to have a handler in my main UI thread which will keep updating the TextView, but when I debug my app, it keeps throwing an exception on this line
tvStopwatch.setText(msg.what);
I am quite new to Handlers, so I guess I'm just missing something fundamental.
Here is also the log for the exception
android.content.res.Resources$NotFoundException: String resource ID #0x1
at android.content.res.Resources.getText(Resources.java:1057)
at android.widget.TextView.setText(TextView.java:4186)
at unisoftdevelopment.com.stopwatch.MainActivity$1.handleMessage(MainActivity.java:61)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5279)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
at dalvik.system.NativeStart.main(Native Method)
Please help to find out the problem, thanks in advance.
Common mistake to make: setText() has an overload that takes an integer (which must be a string resource ID, e.g. R.string.my_string).
When you receive the message:
tvStopwatch.setText(msg.what);
It's attempting to resolve msg.what as a string resource, and crashing when it can't be found. You should instead cast the timestamp to a string, and use that instead:
tvStopwatch.setText(String.valueOf(msg.what));
Related
This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 4 years ago.
In my application, I want to run code every 120second and to achieve this I am using Thread.
Following is the code snippet that I am using, but it is giving me Force close error.
Show me error for this line :
getActivity().runOnUiThread(new Runnable() {}
My Codes :
public class MainReminderFragment extends Fragment {
#BindView(R.id.mainExplore_noExploreTxt)
TextView mainExplore_noExploreTxt;
#BindView(R.id.newsPageLoadLay)
RelativeLayout newsPageLoadLay;
#BindView(R.id.toolbarTitleTxt)
TextView toolbarTitleTxt;
private Context context;
public List<Datum> model = new ArrayList<>();
public ReminderAdapter reminderAdapter;
private SharedPrefrencesHandler prefrencesHandler;
private String token = "";
private LinearLayoutManager layoutManager;
private MainActivity mainActivity;
private InterfaceApi api;
private boolean isRunning = false;
public ProgressBar mainExplore_progressBar;
public RecyclerView mainExplore_recyclerView;
public MainReminderFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_main_reminder, container, false);
//Initialize
ButterKnife.bind(this, view);
context = getActivity();
prefrencesHandler = new SharedPrefrencesHandler(context);
reminderAdapter = new ReminderAdapter(context, model);
layoutManager = new LinearLayoutManager(context);
mainExplore_progressBar = view.findViewById(R.id.mainExplore_progressBar);
mainExplore_recyclerView = view.findViewById(R.id.mainExplore_recyclerView);
isRunning = true;
mainActivity = (MainActivity) getActivity();
api = ApiClient.getClient().create(InterfaceApi.class);
//Toolbar name
toolbarTitleTxt.setText(context.getResources().getString(R.string.reminder));
//RecyclerView
mainExplore_recyclerView.setLayoutManager(layoutManager);
mainExplore_recyclerView.setHasFixedSize(true);
//Get token
token = prefrencesHandler.getFromShared(SharedPrefrencesKeys.TOKEN.name());
//Get data
getData();
if (getActivity() != null) {
Thread thread = new Thread() {
#Override
public void run() {
float i;
try {
for (i = 0; i <= 100; i++) {
getActivity().runOnUiThread(new Runnable() {
public void run() {
getData();
}
});
sleep(120000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
}
return view;
}
//Get data
private void getData() {
try {
Call<SerialReminderListResponse> call = api.getListSerialReminder(token, "2", sendData(1));
mainExplore_progressBar.setVisibility(View.VISIBLE);
call.enqueue(new Callback<SerialReminderListResponse>() {
#Override
public void onResponse(Call<SerialReminderListResponse> call, Response<SerialReminderListResponse> response) {
if (response.body().getData() != null) {
if (response.body().getData().size() > 0) {
model.clear();
model.addAll(response.body().getData());
reminderAdapter.notifyDataSetChanged();
mainExplore_recyclerView.setAdapter(reminderAdapter);
//Gone no explore
mainExplore_noExploreTxt.setVisibility(View.GONE);
} else {
mainExplore_noExploreTxt.setVisibility(View.VISIBLE);
mainExplore_recyclerView.setVisibility(View.GONE);
}
}
mainExplore_progressBar.setVisibility(View.GONE);
}
#Override
public void onFailure(Call<SerialReminderListResponse> call, Throwable t) {
mainExplore_progressBar.setVisibility(View.GONE);
}
});
} catch (Exception e) {
}
}
Force close error in logCat :
Process: in.nouri.sevenwatchlist, PID: 7104
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v4.app.FragmentActivity.runOnUiThread(java.lang.Runnable)' on a null object reference
at com.app.test.Fragments.MainPageFrags.MainReminderFragment$1.run(MainReminderFragment.java:136)
Your below code
getActivity().runOnUiThread(new Runnable() {
public void run() {
getData();
}
});
sleep(120000);
Replce With:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
getData();
}
}, 120000);
Try this one.
TextView tv= new TextView(this);
tv.postDelayed(sendData, 1000);
Handler handler = new Handler();
private Runnable sendData=new Runnable(){
public void run(){
try {
//prepare and send the data here..
handler.removeCallbacks(sendData);
handler.postDelayed(sendData, 12000);
}
catch (Exception e) {
e.printStackTrace();
}
}
};
I recommend using ScheduledExecutorService as it is based on nano time, hence more accurate. Example:
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool( 1 );
final Runnable r = new Runnable() {
public void run() {
// execution point
}
};
scheduler.scheduleAtFixedRate(r, 0, 1000, MILLISECONDS);
Trying to run MIDI on my Android app. I'm following the midisuite example to configure my app and it works fine with the exception of aftertouch. Whenever I try to trigger aftertouch, I run into a threading exception type
InteruptedException. How should I prevent this threading issue? My knowledge on multithreading isn't the best or else I would've figured this out already. All I can really tell right now is that the message is sending too fast and the thread hasn't woken up yet from its sleep call.
I followed the github repo with my code as follows:
MidiReceiver subclass:
#TargetApi(Build.VERSION_CODES.M)
public class MidiEngine extends MidiReceiver {
public AudioActivity activity;
private MidiEventScheduler eventScheduler;
private MidiFramer midiFramer;
private MidiReceiver midiReceiver = new MyReceiver();
private Thread mThread;
private boolean go;
private int mProgram;
public MidiEngine() {
this(new AudioActivity());
}
public MidiEngine(AudioActivity activity) {
this.activity = activity;
midiReceiver = new MyReceiver();
midiFramer = new MidiFramer(midiReceiver);
}
public AudioActivity getActivity() {
return this.activity;
}
/* This will be called when MIDI data arrives. */
#Override
public void onSend(byte[] data, int offset, int count, long timestamp)
throws IOException {
if (eventScheduler != null) {
if (!MidiConstants.isAllActiveSensing(data, offset, count)) {
eventScheduler.getReceiver().send(data, offset, count,
timestamp);
}
}
}
// Custom Listener to send to correct methods
private class MyReceiver extends MidiReceiver {
#Override
public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
byte command = (byte)(msg[0] & MidiConstants.STATUS_COMMAND_MASK);
int channel = (byte)(msg[0] & MidiConstants.STATUS_CHANNEL_MASK);
switch (command) {
case MidiConstants.STATUS_NOTE_ON:
activity.keyDown(i, msg[1], msg[2]);
break;
case MidiConstants.STATUS_NOTE_OFF:
activity.keyUp(channel, msg[1]);
break;
case MidiConstants.STATUS_POLYPHONIC_AFTERTOUCH:
activity.keyDown(channel, msg[1], msg[2]);
break;
case MidiConstants.STATUS_PITCH_BEND:
activity.pitchBendAction(channel, (msg[2] << 7) + msg[1]);
break;
case MidiConstants.STATUS_CONTROL_CHANGE:
activity.ccAction(channel, msg[1], msg[2]);
break;
case MidiConstants.STATUS_PROGRAM_CHANGE:
mProgram = msg[1];
break;
default:
break;
}
}
}
class MyRunnable implements Runnable {
#Override
public void run() {
do {
try {
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
try {
processMidiEvents();
}
catch (Exception e) {
Log.e("Java", "SynthEngine background thread exception.", e);
}
}
});
Thread.sleep(100);
}
catch (InterruptedException e) {
Log.e("Java", "Threading exception", e);
}
}
while (go);
}
}
/**
* #throws IOException
*
*/
private void processMidiEvents() throws IOException {
long now = System.nanoTime();
MidiEventScheduler.MidiEvent event = (MidiEventScheduler.MidiEvent) eventScheduler.getNextEvent(now);
while (event != null) {
midiFramer.send(event.data, 0, event.count, event.getTimestamp());
eventScheduler.addEventToPool(event);
event = (MidiEventScheduler.MidiEvent) eventScheduler.getNextEvent(now);
}
}
public void start() {
stop();
go = true;
mThread = new Thread(new MyRunnable());
mThread.setPriority(6);
eventScheduler = new MidiEventScheduler();
mThread.start();
}
public void stop() {
go = false;
if (mThread != null) {
try {
mThread.interrupt();
mThread.join(500);
}
catch (Exception e) {
}
mThread = null;
eventScheduler = null;
}
}
}
Stack Trace Error (line 154 refers to the Thread.sleep part in my custom Runnable class):
Java: Threading exception
java.lang.InterruptedException
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:1031)
at java.lang.Thread.sleep(Thread.java:985)
at com.rfoo.midiapp.communication.MidiEngineInput$MyRunnable.run(MidiEngineInput.java:154)
at java.lang.Thread.run(Thread.java:818)
Thanks!
EDIT: Thread start
Midi Device Service subclass (thread will start whenever a device has connected or disconnected).
#TargetApi(Build.VERSION_CODES.M)
public class MidiSynthDeviceService extends MidiDeviceService {
private static final String TAG = "MidiSynthDeviceService";
private boolean midiStarted = false;
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onDestroy() {
AudioActivity.midiEngine.stop();
super.onDestroy();
}
#Override
// Declare the receivers associated with your input ports.
public MidiReceiver[] onGetInputPortReceivers() {
return new MidiReceiver[] { AudioActivity.midiEngine };
}
/**
* This will get called when clients connect or disconnect.
* You can use it to turn on your synth only when needed.
*/
#Override
public void onDeviceStatusChanged(MidiDeviceStatus status) {
if (status.isInputPortOpen(0) && !midiStarted) {
AudioActivity.midiEngine.start();
midiStarted = true;
} else if (!status.isInputPortOpen(0) && midiStarted){
AudioActivity.midiEngine.stop();
midiStarted = false;
}
}
}
Activity class:
public class AudioActivity extends AppCompatActivity {
private Thread thread;
public static MidiEngine midiEngine;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Layout inits
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
// Setup MIDI:
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI)) {
Toast.makeText(this, "MIDI not supported!", Toast.LENGTH_LONG).show();
}
else {
midiEngine = new MidiEngine(this);
setupMidi();
}
// Setup audio thread:
if (thread == null) {
thread = new Thread() {
public void run() {
setPriority(Thread.MAX_PRIORITY);
// Runs an Open SL audio thread (C++)
// This generates a waveform.
// AudioEngine is a wrapper class connecting C++ to Java
AudioEngine.runProcess();
}
}
}
}
public void setupMidi() {
if (activity == null) activity = (AudioActivity) getContext();
mMidiManager = (MidiManager) activity.getSystemService(AudioActivity.MIDI_SERVICE);
if (mMidiManager == null) {
Toast.makeText(activity, "MidiManager is null!", Toast.LENGTH_LONG).show();
return;
}
// Get Device Info
MidiDeviceInfo deviceInfo = MidiTools.findDevice(mMidiManager, "RFOO", "AudioApp");
// MIDI Input
portIndex = 0;
inputPortSelector = new MidiOutputPortConnectionSelector(mMidiManager, activity, R.id
.inputListView, deviceInfo, portIndex);
inputPortSelector.setConnectedListener(new MyPortsConnectedListener());
midi_ch_input = 0;
midi_ch_output = 0;
}
// Bunch of UI code here....
}
I've noticed a bug in a basic survey app I'm making to better learn android.
Occasionally I get a W/System.errīš at MainActivity.surveyAvailable(MainActivity.java:40) that points to this line of code:
button.setVisibility(View.GONE);
I've used setVisibility many times before and never had any issues.
Here's the function, this gets called when the user first enters the app, and after they finish taking a survey to check the server and see if there is another survey available for the user:
public void surveyAvailable(boolean surveyIsAvailable) {
Log.d("MainActivity", "App survey is available? " + surveyIsAvailable );
Button button = (Button)findViewById(R.id.takeSurveyButton);
if (surveyIsAvailable) {
button.setVisibility(View.VISIBLE);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
App.getInstance().showSurvey();
}
});
} else {
Log.d("MainActivity", "We hit here");
button.setVisibility(View.GONE);
}
}
When a survey isn't available, the appropriate lines are logged - App survey is available? false and 'We hit here'. But then the button sometimes doesn't get set to View.GONE and I see the System.Err line. But sometimes it works fine and the button's visibility does change. Any idea how to fix that? Or how to get more information on what the System.Err actually means?
EDIT:
I found that by setting Button surveyButton; in my activity and then referencing the button as this.surveyButton seems to get the functionality to work more along the lines of what we'd expect (e.g. when we call button.setVisibility(View.GONE) the view is actually consistently GONE). But it still throws the System.Err line which has me hesitant that things are working correctly.
Edited Activity:
public class MainActivity extends ActionBarActivity implements SurveyListener {
Button surveyButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.surveyButton = (Button)findViewById(R.id.takeSurveyButton);
}
public void surveyAvailable(boolean surveyIsAvailable) {
Log.d("MainActivity", "App survey is available? " + surveyIsAvailable );
if (surveyIsAvailable) {
this.surveyButton.setVisibility(View.VISIBLE);
this.surveyButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
App.getInstance().showSurvey();
}
});
} else {
Log.d("MainActivity", "We hit here");
this.surveyButton.setVisibility(View.GONE);
}
}
}
The activity implements this class:
public abstract interface SurveyListener
{
public abstract void surveyAvailable(boolean surveyAvailable);
}
Main App class that checks for surveys and calls 'surveyAvailable()`:
public class App
{
private static App _instance;
private SurveyListener _eventsHandler;
private String _apiKey = "";
private String _appuserId = "";
private String _surveyUrl = "";
private Activity _parentContext;
private Boolean _surveyAvailable;
public static App initWithApiKeyAndListener(String apiKey, SurveyListener surveyEventsHandler) {
if (_instance == null)
{
_instance = new App();
_instance._parentContext = (Activity) surveyEventsHandler;
_instance.setSurveyListener(surveyEventsHandler);
_instance.setApiKey(apiKey);
String appuserId = PreferenceManager.getDefaultSharedPreferences((Activity) _instance._eventsHandler).getString(tag, "no_appuser");
if (appuserId == "no_appuser") {
_instance._surveyAvailable = true;
_instance.alertAvailability(true);
} else {
_instance.checkForCampaigns();
}
}
return _instance;
}
private void alertAvailability(boolean surveyAvailable) {
App.getInstance()._eventsHandler.surveyAvailable(surveyAvailable);
}
private void checkForCampaigns() {
new CampaignCheck().execute();
}
public static App getInstance()
{
if (_instance == null)
{
_instance = new App();
}
return _instance;
}
public void donePushed()
{
App.getInstance().checkForCampaigns();
}
private class CampaignCheck extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... params) {
Boolean surveysAvailable = false;
try {
surveysAvailable = new AppuserConnection().checkCampaigns();
App.getInstance()._surveyAvailable = surveysAvailable;
App.getInstance().alertAvailability(_surveyAvailable);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
}
}
}
You shouldn't modify the UI elements from a different thread. You are doing this by calling App.getInstance().alertAvailability(_surveyAvailable); on a background thread. Move this to the AsyncTask's onPostExecute.
So am trying to use runOnUiThread() to update my LogUI (found in my apps MainActivity) which is a TextView. The issue is am trying to update the TextView using runOnUiThread() from another class by getting the strings to be made on the View.
Here is details of my code to elaborate my issue:
private LoggingClass getLogs;
getLogs.AddtoLogUI(String.format("Established on port: %d", obj));
Then the LoggingClass code:
public class LoggingClass {
private MainActivity updateUI;
private String stringValue;
public void AddtoLogUI(final String format) {
this.stringValue = format;
updateUI.runOnUiThread(new Runnable(){
#Override
public void run() {
MainActivity.log_this(stringValue);
}
});
}
}
The method MainActivity.log_this() code is like this:
public static void log_this(final String msg){
if(editable.toString().split("\n").length >=50) {
editable.delete(0, editable.toString().indexOf("\n"));
}
Runnable runnable = new Runnable(){
#Override
public void run() {
editable.append(msg);
editable.append("\n");
}
};
LogView.post(runnable);
}
PS: LogView is a TextView.
The NullpointerException is thrown when am trying to get the Strings using the getLogs.AddtoLogUI() method.
Any suggestions?
Additional Info as regards the object:
`Object obj[] = new Object[1];
obj[0] = Integer.valueOf(Port);`
You declare private LoggingClass getLogs; in your Activity
getLogs=new LoggingClass (MainActivity.this) in your onCreate
then u can use getLogs.AddtoLogUI(String.format("Established on port: %d", obj));
createLoggingClass constructor
public class LoggingClass {
private MainActivity updateUI;
private String stringValue;
public LoggingClass (MainActivity updateUI){
this.updateUI=updateUI;
}
public void AddtoLogUI(final String format) {
this.stringValue = format;
updateUI.runOnUiThread(new Runnable(){
#Override
public void run() {
MainActivity.log_this(stringValue);
}
});
}
}
I solved the issue myself using a handler instead. Here is a sample code of what I did:
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
public void run() {
// UI code goes here
}
});
Right now, when I change the activity, my thread seams to go to sleep or something. And when I come back to the main activity, there are two threads running, doing the same things. I'm not sure if this is the case, but it seems like it's something equal.
...
public class MainActivity extends Activity {
public static double cowCount = 195;
public static double income = 0.100;
static boolean twiceCow = false, Threadrunning = false;
...
public void inc() {
new Thread(new income()).start();
}
class income implements Runnable {
#Override
public void run() {
for (int i = 0; i < 20;) {
final int value = i;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
#Override
public void run() {
cowCount = cowCount + income;
refresh();
}
});
}
}
}
This is how my thread looks like.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler();
checkThread();
}
private void checkThread() {
if (Threadrunning == false)
inc();
Threadrunning = true;
}
public void inc() {
new Thread(new income()).start();
}
...
public void refresh () {
TextView myTextView = (TextView)findViewById(R.id.myText);
myTextView.setText("You Have " + String.valueOf((nf.format(cowCount)) + " Cows!"));
}
I don't really understand what I've done wrong.
Please review this post: http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
Consider your activity re-start as the same thing as a config change.
This pattern, i.e. using a retained Fragment as a container for your thread, and proxying UI updates via callbacks to your activity, is a pattern that will work much better for you.
In your case you'd need only a single TaskCallback for your UI refresh(), e.g. onRefreshCowCount(int cows);