I am working on a program in java (android) I would like it to start and continue asking questions, upon button click, until the user is prompted for input, once input is received the program should resume asking the questions. The program should pause when the button is clicked again.
I am new to OOP and self taught. I thought that a thread was going to be the most practical way to solve the issue. I cannot get the program to both loop and allow user input. When trying to use HandlerThread I loose the ability to input data in the EditText. Could someone help with getting this loop to run on start click and loop after input?
I have a functional program that works when the button is cycled:
Main layout begins with "START" button, on click the start button turns to "PAUSE" and a seperate repeat button is made visible (and functional).
The values are generated, the question is asked, and the user is prompted by popping up the soft keyboard and setting the cursor in the EditText field.
Once an answer is received and the "enter/done" keyboard button is clicked, the answer will be evaluated against the saved values. But I cannot get the program to loop, or if I get it to loop it skips input completely and continues to as questions with no time for input.
Please direct me on code cleanliness if needed, I want to learn what I am doing incorrectly.
MAIN.java
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.util.Locale;
public class MainActivity extends AppCompatActivity {
// Create appropriate objects for buttons, edit text, and text to speech
TextToSpeech tts;
EditText txt;
Button sbtn, rbtn;
// Array and int to store numbers
int[] num;
int added = 0;
// Boolean to check if questions is running
public boolean isRunning;
// Variables for random number range. TODO(Put into switch statement and list to select 1, 10, or 100s)
static int maxNum = 100;
static int minNum = 10;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initializing buttons and others
txt = findViewById(R.id.ans);
sbtn = findViewById(R.id.strButton);
rbtn = findViewById(R.id.rptButton);
rbtn.setVisibility(View.INVISIBLE);
// Initialize text to speech engine
tts = new TextToSpeech(getApplicationContext(), new TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
if (status != TextToSpeech.ERROR) {
tts.setLanguage(Locale.ENGLISH);
}
}
});
// Start button click listener
sbtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (!isRunning) {
// Show repeat button, hide start and show pause
rbtn.setVisibility(View.VISIBLE);
sbtn.setText("Pause");
process();
isRunning = true;
} else {
sbtn.setText("Start");
rbtn.setVisibility(View.GONE);
isRunning = false;
}
}
});
// Repeat button click listener
rbtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Repeat recently generated numbers
Utilities.speakNums(num[0], num[1], tts);
}
});
}
public void onPause() {
if (tts != null) {
tts.stop();
tts.shutdown();
}
super.onPause();
}
// Get input and compare with stored values, announce if user answer is correct or incorrect
public void submitAns() {
txt.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
// If the event is a key-down event on the "enter/done" button
if ((event.getAction() == KeyEvent.ACTION_DOWN) &&
(keyCode == KeyEvent.KEYCODE_ENTER)) {
// Check to make sure the text field is not empty
if (TextUtils.isEmpty(txt.getText().toString())) {
Toast.makeText(MainActivity.this, "Enter a Number!", Toast.LENGTH_SHORT).show();
return false;
}
int intValue = Integer.parseInt(txt.getText().toString());
if (added == intValue) {
Toast.makeText(MainActivity.this, "Correct", Toast.LENGTH_SHORT).show();
tts.speak("Correct", TextToSpeech.QUEUE_ADD, null, null);
} else {
Toast.makeText(MainActivity.this, added + " is the Correct Answer!", Toast.LENGTH_SHORT).show();
tts.speak("Incorrect", TextToSpeech.QUEUE_FLUSH, null, null);
Utilities.speakAns(added, tts);
tts.speak("is the Correct answer", TextToSpeech.QUEUE_ADD, null, null);
}
txt.setText("");
return true;
}
return false;
}
});
}
public void process() {
num = Utilities.askQuestion(minNum, maxNum, tts);
added = Utilities.add(num[0], num[1]);
Utilities.focus(txt, getApplicationContext());
submitAns();
}
}
UTILITIES.java
import android.content.Context;
import android.speech.tts.TextToSpeech;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import java.util.Random;
public class Utilities {
// Function to generate random numbers in range
public static int randomGen(int minNum, int maxNum) {
final Random randNum = new Random();
return randNum.nextInt(maxNum - minNum) + minNum;
}
public static int add(int num1, int num2) {
return num1 + num2;
}
public static int sub(int num1, int num2) {
return num1 - num2;
}
// Speak individual numbers with operator in between speech
public static void speakNums(int r1, int r2, TextToSpeech tts) {
String toSpeak = Integer.toString(r1);
String nexToSpeak = Integer.toString(r2);
tts.speak(toSpeak, TextToSpeech.QUEUE_ADD, null, null);
tts.speak("Plus", TextToSpeech.QUEUE_ADD, null, null);
tts.speak(nexToSpeak, TextToSpeech.QUEUE_ADD, null, null);
}
// Speak answer
public static void speakAns(int a, TextToSpeech tts) {
String sumSpeak = Integer.toString(a);
tts.speak(sumSpeak, TextToSpeech.QUEUE_ADD, null, null);
}
// Request focus so that keyboard pops up as generate button is tapped
public static void focus(EditText txt, Context context) {
txt.requestFocus();
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(txt, InputMethodManager.SHOW_IMPLICIT);
}
// Generate question, speak question, and return array of random numbers for other operations
public static int[] askQuestion(int minNum, int maxNum, TextToSpeech tts) {
int r1 = randomGen(minNum, maxNum);
int r2 = randomGen(minNum, maxNum);
speakNums(r1, r2, tts);
return new int[] { r1, r2};
}
}
If you want to have a repeating/infinite series of events that involve user interaction (like answering a question) you should set it up as a series of chained events rather than trying to use an actual code loop or handler thread.
An example of how this could be set up is:
Generate a question to show (e.g. "What is 12 + 54?")
When the user is done answering, call a "submit" method that checks their answer and either shows an error or generates a new question to show.
Repeat the cycle above for as long as you want. No loops or handler threads are needed for this.
In terms of architecture, separating out as much of the question-generation and answer-processing logic into a ViewModel will help you tremendously, then the activity can just observe the relevant state in the view model (like what question to show).
Here is a simple example based on the description and example code you provided. There is a ViewModel that handles creating a question, checking the answer, and advancing to a new question, and an Activity that observes the relevant state from the ViewModel. Try it out in a blank app project to understand how it works.
ViewModel
public class MainViewModel extends ViewModel {
// Error string to observe - to show an error message or toast
private final MutableLiveData<String> error = new MutableLiveData<>("");
LiveData<String> getError() {
return error;
}
// Current question to show
private final MutableLiveData<String> question = new MutableLiveData<>("");
LiveData<String> getQuestion() {
return question;
}
// Text to show on the start/pause button
private final MutableLiveData<String> startPauseButton = new MutableLiveData<>("START");
LiveData<String> getStartPauseButton() {
return startPauseButton;
}
// private internal state, e.g. current question,
// expected answer, play/pause state
private int expected = 0;
private String current_question = "";
private boolean playing = false;
private final Random random = new Random();
private final int minNum = 10;
private final int maxNum = 100;
private int getNumber() {
return random.nextInt(maxNum - minNum) + minNum;
}
// Process a user's answer, and either show an error
// message or generate a new question to show
void submitAnswer(String ans) {
try {
int a = Integer.parseInt(ans);
if( a == expected ) {
generateNewQuestion();
question.postValue(current_question);
}
else {
error.postValue("Incorrect answer, try again");
}
}
catch (NumberFormatException e) {
error.postValue("Not a number - enter a number");
}
}
private void generateNewQuestion() {
int a = getNumber();
int b = getNumber();
expected = a + b;
current_question = "What is " + a + " + " + b + "?";
}
void clearError() {
error.postValue("");
}
// Called when the user clicks the play/pause button
void clickStartPause() {
playing = !playing;
if( playing ) {
startPauseButton.postValue("PAUSE");
question.postValue(current_question);
}
else {
startPauseButton.postValue("START");
question.postValue("");
}
}
public MainViewModel() {
generateNewQuestion();
}
}
Activity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView question = findViewById(R.id.question);
EditText answer = findViewById(R.id.answer);
Button start = findViewById(R.id.start);
Button submit = findViewById(R.id.submit);
question.setVisibility(View.GONE);
answer.setVisibility(View.GONE);
submit.setVisibility(View.GONE);
MainViewModel model = new ViewModelProvider(this).get(MainViewModel.class);
// Observe the current question, and if it is blank
// hide the question/answer/submit views
final Observer<String> questionObserver = questionTxt -> {
if( questionTxt == null || questionTxt.isEmpty() ) {
question.setVisibility(View.GONE);
answer.setVisibility(View.GONE);
submit.setVisibility(View.GONE);
}
else {
question.setVisibility(View.VISIBLE);
answer.setVisibility(View.VISIBLE);
submit.setVisibility(View.VISIBLE);
question.setText(questionTxt);
}
};
// Observe the error state, if it is non-blank show
// a toast then reset the state (so the toast only shows once)
final Observer<String> errorObserver = errorText -> {
if( errorText != null && !errorText.isEmpty() ) {
Toast.makeText(this, errorText, Toast.LENGTH_LONG).show();
model.clearError();
}
};
model.getError().observe(this, errorObserver);
model.getQuestion().observe(this, questionObserver);
model.getStartPauseButton().observe(this, start::setText);
submit.setOnClickListener(v -> {
model.submitAnswer(answer.getText().toString());
answer.setText("");
});
start.setOnClickListener(v -> model.clickStartPause());
}
XML
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/question"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<EditText
android:id="#+id/answer"
android:hint="Answer"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/question"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<Button
android:id="#+id/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/answer"/>
<Button
android:id="#+id/submit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Submit"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/answer"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Related
I am making a quiz application and using the ArrayList with. I have a problem with the Answers: it is working when I have just one answer, but what can I do if the question has two answers?
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.util.ArrayList;
import cn.pedant.SweetAlert.SweetAlertDialog;
public class MainActivity extends AppCompatActivity {
TextView questionLabel, questionCountLabel, scoreLabel;
EditText answerEdt;
Button submitButton;
ProgressBar progressBar;
ArrayList<QuestionModel> questionModelArraylist;
int currentPosition = 0;
int numberOfCorrectAnswer = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
questionCountLabel = findViewById(R.id.noQuestion);
questionLabel = findViewById(R.id.question);
scoreLabel = findViewById(R.id.score);
answerEdt = findViewById(R.id.answer);
submitButton = findViewById(R.id.submit);
progressBar = findViewById(R.id.progress);
questionModelArraylist = new ArrayList<>();
setUpQuestion();
setData();
submitButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
checkAnswer();
}
});
answerEdt.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
// If the event is a key-down event on the "enter" button
Log.e("event.getAction()",event.getAction()+"");
Log.e("event.keyCode()",keyCode+"");
if ((event.getAction() == KeyEvent.ACTION_DOWN) &&
(keyCode == KeyEvent.KEYCODE_ENTER)) {
checkAnswer();
return true;
}
return false;
}
});
}
public void checkAnswer(){
String answerString = answerEdt.getText().toString().trim();
if(answerString.equalsIgnoreCase(questionModelArraylist.get(currentPosition).getAnswer())){
numberOfCorrectAnswer ++;
new SweetAlertDialog(MainActivity.this, SweetAlertDialog.SUCCESS_TYPE)
.setTitleText("Sehr gut!")
.setContentText("Richtig!")
.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
#Override
public void onClick(SweetAlertDialog sweetAlertDialog) {
currentPosition ++;
setData();
answerEdt.setText("");
sweetAlertDialog.dismiss();
}
})
.show();
}else {
new SweetAlertDialog(MainActivity.this, SweetAlertDialog.ERROR_TYPE)
.setTitleText("Falsch :(")
.setContentText("Die Richtige Antwort ist : "+questionModelArraylist.get(currentPosition).getAnswer())
.setConfirmText("OK")
.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
#Override
public void onClick(SweetAlertDialog sDialog) {
sDialog.dismiss();
currentPosition ++;
setData();
answerEdt.setText("");
}
})
.show();
}
int x = ((currentPosition+1) * 100) / questionModelArraylist.size();
progressBar.setProgress(x);
}
public void setUpQuestion(){
questionModelArraylist.add(new QuestionModel("Write one planet located between Earth and the sun","Venus or Mercury"));
questionModelArraylist.add(new QuestionModel("the 5th planet from the sun","Jupiter"));
questionModelArraylist.add(new QuestionModel("write names of any two oceans","Atlantic Ocean or Arctic Ocean or Indian Ocean or Pacific Ocean orSouthern Ocean"));
}
public void setData(){
if(questionModelArraylist.size()>currentPosition) {
questionLabel.setText(questionModelArraylist.get(currentPosition).getQuestionString());
scoreLabel.setText("Ergebnis :" + numberOfCorrectAnswer + "/" + questionModelArraylist.size());
questionCountLabel.setText("Frage Nummer : " + (currentPosition + 1));
}else{
new SweetAlertDialog(MainActivity.this, SweetAlertDialog.SUCCESS_TYPE)
.setTitleText("Du bist Fertig :)")
.setContentText("dein Ergebnis ist : "+ numberOfCorrectAnswer + "/" + questionModelArraylist.size())
.setConfirmText("Wiederholen")
.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
#Override
public void onClick(SweetAlertDialog sDialog) {
sDialog.dismissWithAnimation();
currentPosition = 0;
numberOfCorrectAnswer = 0;
progressBar.setProgress(0);
setData();
}
})
.setCancelText("schließen")
.setCancelClickListener(new SweetAlertDialog.OnSweetClickListener() {
#Override
public void onClick(SweetAlertDialog sDialog) {
sDialog.dismissWithAnimation();
finish();
}
})
.show();
}
}
}
Question Class
public class QuestionModel {
public QuestionModel(String questionString, String answer) {
QuestionString = questionString;
Answer = answer;
}
public String getQuestionString() {
return QuestionString;
}
public void setQuestionString(String questionString) {
QuestionString = questionString;
}
public String getAnswer() {
return Answer;
}
public void setAnswer(String answer) {
Answer = answer;
}
private String QuestionString;
private String Answer;
}
As you can see, I want to make the answer in the first Question to be Mercury or Venus,the 2nd Question is Jupiter and the 3d Question has 5 Answers "Atlantic Ocean or Arctic Ocean or Indian Ocean or Pacific Ocean or Southern Ocean"
how can i make it to work ? Thank you.
Basically: by changing your data model.
You see, you create your classes to "model" reality. If you want to allow for multiple answers, then a simple one to one mapping (as QuestionModel implies) simply doesn't work. Then your model should be 1:n, like 1 question string, but the answers could be a List<String> for example. For questions with only one answer, that list simply contains only a single entry.
That is the way to go there: first you think up how your data needs to be organized, then you build everything, including your UI structures around that.
#GhostCat is correct, you must analize before implementing. For your specific situation i'm thinking that Enumerations might be helpful. I am not entirely clear about your goal but that is how i would initially approach it.
And to generalize it in your QuestionModel class I would first create an Interface to describe any possible answer.
interface AnswerInterface {
val value: String
}
I would use generics that extend this interface and my QuestionModel then would become as this.
class QuestionModel<QuestionAnswer : AnswerInterface>(
var question: String,
vararg answers: QuestionAnswer
)
The QuestionAnswer can be any class that implements the AnswerInterface and the varard means i can have any number of intances as answers.
With this in mind, my answers enum would be:
enum class PlanetAnswers(override val value: String) : AnswerInterface {
VENUS("Venus"),
MERCURY("Mercury"),
JUPITER("Jupiter")
}
Finally, everything ties together like this:
val question1 = QuestionModel("One planet between Earth and Sun?", PlanetAnswers.VENUS, PlanetAnswers.MERCURY)
val question2 = QuestionModel("The 5th planet from the Sun?", PlanetAnswers.JUPITER)
I have a question and any number of answers, but they all must come from the same enum. I can create more enums and then create more questions with different set of answers.
I have declared my variable, 'changed' too null so that I can check if it changes when the edittext is changed,
The problem I think is, when I press either the save button or the cancel button, it will always produce the value null, as upon clicking the button it is still null. However, I thought that the textwatcher would listen to the EditText and even if nothing was changed in the EditText it would by default change the SetChanged() to false as it provided "live updates", however clearly this isn't the case, am I doing something wrong? or am I supposed to approach it in a different way?, is there some way of refreshing it?
Advise would be greatly appreciated.
(P.S Some code was deleted to reduce the size and make it look easy on the eye, so excuse me for any missing braces. Furthermore, the activity does run properly as it shows the layout.However upon pressing any of the buttons it causes it to crash.)
public class EditNewItemActivity extends AppCompatActivity{
private Boolean changed = null;
private TextView title,content;
private Button saveBtn,cancelBtn;
private String date;
private int id;
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_item);
title = (TextView) findViewById(R.id.editItemTitle);
content = (TextView) findViewById(R.id.editItemDescription);
saveBtn = (Button) findViewById(R.id.editItemSaveBtn);
cancelBtn = (Button) findViewById(R.id.editItemCancelBtn);
Bundle extras = getIntent().getExtras();
title.setText(extras.getString("title"));
content.setText(extras.getString("content"));
date = extras.getString("date");
id = extras.getInt("id");
GenericTextWatcher textWatcher = new GenericTextWatcher();
title.addTextChangedListener(textWatcher);
content.addTextChangedListener(textWatcher);
ClickEvent clickEvent = new ClickEvent();
saveBtn.setOnClickListener(clickEvent);
cancelBtn.setOnClickListener(clickEvent);
}
private class ClickEvent implements View.OnClickListener{
#Override
public void onClick(View v) {
switch (v.getId()){
case R.id.editItemSaveBtn:
save();
break;
case R.id.editItemCancelBtn:
cancel();
break;
}
}
}
private void cancel() {
if (getChanged() == null){
//This was used to simply verify that getchanged was still null.
}
if (title.getText().toString() != "" || content.getText().toString() != ""){
if (getChanged() == false) {
// if nothing has been changed let it cancel etc
}else {
}
}
}
private void save() {
if (tempTitle != "" || tempContent != "") {
if(getChanged() == true){
}
}
public Boolean getChanged() {
return changed;
}
public void setChanged(Boolean changed) {
this.changed = changed;
}
private class GenericTextWatcher implements TextWatcher{
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
Log.v("Beforetext:", s.toString());
EditNewItemActivity editItem = new EditNewItemActivity();
editItem.setChanged(false);
}
#Override
public void afterTextChanged(Editable s) {
Log.v("afterTextChanged:", s.toString());
EditNewItemActivity editItem = new EditNewItemActivity();
editItem.setChanged(true);
Log.v("Status:", editItem.getChanged().toString());
}
}
You had change the changed. But which you changed is in your new EditNewItemActivity not in your current page.
This is where you made mistake (beforeTextChanged and afterTextChanged in your GenericTextWatcher):
EditNewItemActivity editItem = new EditNewItemActivity();
editItem.setChanged(false); //or true
You should just call:
setChanged(false); // or true
In fact, you should not new an activity yourself, activity must be create by the Android Framework so that it can be managed by the system.
My app has an input where the user can enter a port number,I tried out of curiosity inputting more than 9 digits of number (Ex: 1234567890),and the app will crash.How do I prevent it from crashing?
EDIT
I am using Java language for Android
CODING
public class addActivity extends Activity{
EditText savedName,savedIP,savedPort,savedUserID,savedUserPass,savedChannel;
Button btnSave;
Button btnBack;
String addressList;
FileOutputStream outputStream;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add);
savedName = (EditText)findViewById(R.id.editName);
savedIP = (EditText)findViewById(R.id.editIP);
savedPort = (EditText)findViewById(R.id.editPort);
savedUserID = (EditText)findViewById(R.id.userID);
savedUserPass = (EditText)findViewById(R.id.userPass);
savedChannel = (EditText)findViewById(R.id.editChannel);
btnSave = (Button)findViewById(R.id.btnSave);
btnBack = (Button)findViewById(R.id.btnBack);
try{
String channelNumString = savedChannel.getText().toString();
int channelNum = Integer.parseInt(String.valueOf(channelNumString));
addressList = savedName.getText().toString();
outputStream = openFileOutput(addressList, Context.MODE_PRIVATE);
//outputStream.write(savedName.getText().toString().getBytes());
outputStream.write("4-".getBytes());
outputStream.write(savedIP.getText().toString().getBytes());
outputStream.write(":".getBytes());
outputStream.write(savedPort.getText().toString().getBytes());
outputStream.write("/user=".getBytes());
outputStream.write(savedUserID.getText().toString().getBytes());
outputStream.write("&password=".getBytes());
outputStream.write(savedUserPass.getText().toString().getBytes());
outputStream.write(System.getProperty("line.separator").getBytes());
outputStream.close();
Toast.makeText(getBaseContext(), "Address Saved !", Toast.LENGTH_SHORT).show();
savedName.setText("");
savedIP.setText("");
savedPort.setText("");
savedUserID.setText("");
savedUserPass.setText("");
savedChannel.setText("");
else{
Toast.makeText(getBaseContext(),"Apps does not support " + channelNumString + " channels",Toast.LENGTH_LONG).show();
}
}catch (Exception e){
e.printStackTrace();
}
LAYOUT
<EditText
android:layout_width="350dp"
android:layout_height="wrap_content"
android:id="#+id/editChannel"
android:background="#android:drawable/edit_text"
android:maxLines="1"
android:layout_gravity="center_horizontal"
android:inputType="number"
android:digits="0123456789."/>
This is the part I'm having problem at
#Fay Zan
Basically you don't want user to input more than 9 digits for your field,
This can be achieved using two ways programmatically or using layout views properties,
In you xml simple give this attribute to your EditText
android:maxLength="9"
OR programmatically by checking the length of your field. for example suppose the id of your field is #id+/portNo
then do validation like this
private EditText mportNo;
inside your onCreate()
mportNo = (EditText) findViewById(R.id.portNo);
String portValue = mportNo.getText().toString();
if(portValue.length() > 9) {
// throw error
}
I Assumed you are using EditText. so you need to use addTextChangedListener().
Try the following way,
YOUR_EDIT_TEXT.addTextChangedListener(new TextWatcher()
{
public void afterTextChanged(Editable s) {}
public void beforeTextChanged(CharSequence s, int start,int count, int after){}
public void onTextChanged(CharSequence s, int start,int before, int count)
{
if(s.length() > 9)
{
//Do your stuff .
// You may show the intimation to user like don't try with more than 9 digits
}
else
{
//Do your stuff
}
}
});
This may helps you.
I'm developing a "guess the number" app, it generates a random number between 1 and 10,000 and you have to try guessing, it will tell you if your prediction it is too big , etc
But when you press the button to probe your number, it generates a new random number every time you press the button.Keep in mind i'm a newbie so i'm learning java for android, but i want to know how to make this simple app.
Here's my code:
package com.boodle.guessthenumber;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public void guess (View view){
EditText textguess = (EditText) findViewById ( R.id.textguess );
TextView resulta = (TextView) findViewById(R.id.resulto);
String guessStr = textguess.getText().toString();
int theGuess = Integer.parseInt(guessStr);
int rand = (int) (Math.random()*10000+1);
if (theGuess > rand) {
resulta.setText(textguess.getText() + " is too big" );
}
if (theGuess < rand) {
resulta.setText(textguess.getText() + " is too small" );
}
if (rand == theGuess){
resulta.setText(textguess.getText() + " is the answer" );
}
}
}
Create rand as a member variable in your class:
public class MainActivity extends ActionBarActivity {
int rand;
initialize in onCreate():
rand = (int) (Math.random()*10000+1);
remove the initialization in your guess() function:
// not needed anymore:
// int rand = (int) (Math.random()*10000+1);
To make the number persist during orientation changes, add this code to your Activity:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt("rand", rand);
super.onSaveInstanceState(savedInstanceState);
}
and then in onCreate() change your random number generation code to this:
if (savedInstanceState != null)
rand = savedInstanceState.getInt("rand");
else
rand = (int) (Math.random()*10000+1);
After you generate the number you have to store it in a persistent storage, for which you have many options: SharedPreferences (which can be passed between activities), a file, SQLiteDatabase...
When the activity starts, only if the number is not there - generate it!
The solution would be to create your random number in onCreate such that it is only created once and then simply access that variable in your guess method. Modify your code as follows:
public class MainActivity extends ActionBarActivity
{
private int rand;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
rand = (int) (Math.random()*10000+1);
}
// rest of code...
And then in guess remove the initialization and simply access the variable by name:
public void guess (View view)
{
// rest of code...
//int rand = (int) (Math.random()*10000+1);
if (theGuess > rand) {
resulta.setText(textguess.getText() + " is too big" );
}
// rest of code...
}
Also, just as a note, it is not necessary to post all the import statements and other similar code. Only posting the code relevant to your issue is the best way to invite concise answers.
The following solution will generate the number when the activity is started and the number will NOT change when the user rotates the screen. It will also make the activity a little bit more effective.
public class MainActivity extends ActionBarActivity {
TextView mResult;
EditText mTextGuess;
private int mNumber;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
// you find your views in onCreate once, they don't change, it's effective
mResult = (TextView) findViewById(R.id.resulto);
mTextGuess = (EditText) findViewById(R.id.textguess);
// BRO-TIP: Google "Butterknife".
// Now you need to initialize the random number
// BUT you want it to stay the same when user rotates the screen, right?
if (savedInstanceState == null) {
// when the user first opens the app, generate new number
mNumber = (int) (Math.random()*10000+1);
} else {
// otherwise load the previously generated number from saved state
mNumber = savedInstanceState.getInt("mNumber");
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// here you save the number across orientation changes
outState.putInt("mNumber", mNumber);
}
public void guess(View v) {
int theGuess = Integer.parseInt(mTextGuess.getText().toString());
// else-if is better for you: when the first is true, you don't need to check the others and so on...
if (theGuess > rand) {
mResult.setText(textguess.getText() + " is too big" );
} else if (theGuess < rand) {
mResult.setText(textguess.getText() + " is too small" );
} else if (rand == theGuess){
mResult.setText(textguess.getText() + " is the answer" );
}
}
}
Scope of the project
When a user touches the Android screen with two fingers, draw a "Frame" at each touch location with a "cursor" for each frame. Each frame is a custom slider that the cursor will move up and down. All the way up will be 100%, middle will be 0% and all the way down will be -100%. This will be used to control small motors, similar to tank turning, each touch controls a separate motor (sending signals over bluetooth). After a two touch and everything is drawn, I want to be able to lift off either finger, BUT keep the cursor at what ever location it was last at, while the other finger is free to move its cursor. When the last finger is lifted off, everything "hides" and resets to 0%.
Functionality Wanted
On two finger touch, draw separate .pngs under the touch location
After the frames and cursors are drawn, keep track of where they are relative to the frame to determine the percentage.
If a finger is lifted off, keep that fingers cursor at last known location, but the other finger can move it's cursor. Also if the finger is put back down it should be able to move its cursor again.
If both fingers are lifted off of the screen, hide everything and reset percentages to 0%
Functionality Obtained
I can draw the frames and cursors on multitouch
Positions and percentages work fine
Cursors do move properly
What doesn't work
I am unsure if I should have one custom class that handles both touch event or if i should have 2 instances of the custom class each handling their own touch events (I have tried both, the only way i get any "real" functionality is with 1 custom class handling both touch events, the other way doesn't work as intended)
When I only have 1 custom class, It works great, but I have it "hide" everything if both fingers are not on the screen, and sometimes android registers that I have lifted a finger off the screen and this causes me a lot of issues when the frames hide then re appear in a different location
When I use 2 custom classes I touch each custom class would have its own touch event, and i wouldn't have to worry about multitouch if i split the classes evenly between the screen. This was not the case, still need to deal with multitouch
Can someone explain to me how android handles their touch events. from what I have done, it seems if i lay down finger 1, the finger 2, the first finger will register a "ACTION_DOWN" and the second will register a "ACTION_POINTER_2_DOWN", BUT if i life off my first finger, my second finger is "demoted" and now all of the events my second finger registers does not related to "ACTION_POINTER_2" and instead will be "ACTION_DOWN, ACTION_UP, etc". Is this correct?
TouchUI.java
package com.robota.android;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ImageView;
public class TouchUI extends ImageView {
public static final String LEFT_TOUCHUI = "com.robota.android:id/leftTouchUI";
public static final String RIGHT_TOUCHUI = "com.robota.android:id/rightTouchUI";
private String whoAmI = new String();
private MyPoints framePts = new MyPoints();
private MyPoints cursorPts = new MyPoints();
private Bitmap frame;
private Bitmap cursor;
private int frameWidth;
private int frameHeight;
private int cursorHeight;
private boolean pointerDown = false;
private int dy;
public TouchUI(final Context context, final AttributeSet as){
super(context, as);
Log.d("TouchUI", getResources().getResourceName(this.getId()));
whoAmI = new String(getResources().getResourceName(this.getId()));
if(whoAmI.equals(LEFT_TOUCHUI)){
frame = BitmapFactory.decodeResource(getResources(), R.drawable.tank_left);
}else if(whoAmI.equals(RIGHT_TOUCHUI)){
frame = BitmapFactory.decodeResource(getResources(), R.drawable.tank_right);
}
cursor = BitmapFactory.decodeResource(getResources(), R.drawable.cursor);
frameWidth = frame.getWidth();
frameHeight = frame.getHeight();
cursorHeight = cursor.getHeight();
}
public void determinePointers(int x, int y){
framePts.setOrigin(x-frameWidth/2, y-frameHeight/2);
cursorPts.setOrigin(x-frameWidth/2, y-frameHeight/2);
}
#Override
public boolean onTouchEvent(MotionEvent e){
int x = 0;
int y = 0;
Log.d("TouchUI", ">>>>> " + whoAmI);
if(e.getAction() == MotionEvent.ACTION_DOWN){
determinePointers(x,y);
pointerDown = true;
}else if(e.getAction() == MotionEvent.ACTION_UP){
pointerDown = false;
}else if(e.getAction() == MotionEvent.ACTION_MOVE){
dy = (int)e.getY()-framePts.getY();
if(dy <= 0){
dy=0;
}else if(dy+cursorHeight/2 >= frameHeight){
dy=frameHeight;
}
sendMotorSpeed(dy);
}
return true;
}
public void sendMotorSpeed(int dy){
float motor = dy;
motor-=frameHeight;
motor*=-1;
motor = (motor/frameHeight)*255;
PacketController.updateMotorSpeeds(whoAmI, (int)motor);
}
public void onDraw(Canvas canvas){
if(pointerDown){//twoDown){
canvas.drawBitmap(frame, framePts.getX(), framePts.getY(), null);
canvas.drawBitmap(cursor, cursorPts.getX(), (cursorPts.getY()+dy), null);
}
invalidate();
}
private class MyPoints{
private int x = -100;
private int y = -100;
private int deltaY = 0;;
public MyPoints(){
this.x = 0;
this.y = 0;
}
public int getX(){
return this.x;
}
public int getY(){
return this.y;
}
public void setOrigin(int x, int y){
this.x = x;
this.y = y;
}
public int getDeltaY(){
return deltaY;
}
public void setDeltaY(int newY){
deltaY = (newY-y);
Log.d("TouchUI", "DY: " + deltaY);
}
}
}
Main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/parentLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<LinearLayout android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.robota.android.TouchUI xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/leftTouchUI"
android:background="#0000"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_weight="1">
</com.robota.android.TouchUI>
<com.robota.android.TouchUI xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rightTouchUI"
android:background="#0000"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_weight="1">
</com.robota.android.TouchUI>
</LinearLayout>
RobotController.java (Main Activity Class)
package com.robota.android;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
public class RobotController extends Activity {
// Tag used to keep track of class in the Log
private static final String TAG = "robotController_new";
// Boolean to debugging
private static final boolean D = true;
// Intent request codes
private static final int DISCONNECT_DEVICE = 1;
private static final int CONNECT_DEVICE = 2;
private static final int REQUEST_ENABLE_BT = 3;
// Handler Codes
public static final int MESSAGE_READ = 1;
public static final int MESSAGE_WRITE = 2;
// Local Bluetooth Adapter
private BluetoothAdapter bluetoothAdapter = null;
// Bluetooth Discovery and Datahandler
private BluetoothComm btComm = null;
// Debug's TextView, this is where strings will be written to display
private TextView tv;
private ScrollView sv;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
if(D) Log.d(TAG, "++ON CREATE++");
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(bluetoothAdapter == null){
if(D) Log.d(TAG, "NO BLUETOOTH DEVICE");
Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_SHORT).show();
finish();
return;
}
PacketController.controller = this;
}
public void onStart(){
super.onStart();
if(D) Log.d(TAG, "++ON START++");
if(!bluetoothAdapter.isEnabled()){
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
}else{
// Start BluetoothComm
if(btComm == null){
setupComm();
}
}
}
/**
* Creates new Bluetooth Communication
*/
private void setupComm(){
if(D) Log.d(TAG, "+++setupComm+++");
btComm = new BluetoothComm(this, handler);
}
private void connectDevice(Intent data){
if(D) Log.d(TAG, "+++connectDevice+++");
String addr = data.getExtras()
.getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(addr);
if(D) Log.d(TAG,"REMOTE ADDR: "+ addr);
btComm.connect(device);
}
private void disconnectDevice(){
if(D) Log.d(TAG, "---disconnectDevice---");
if(btComm.getState() == btComm.STATE_CONNECTED){
btComm.disconnect();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
//super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent serverIntent = null;
switch(item.getItemId()){
case R.id.insecure_connect_scan:
// Launch the DeviceListActivity to see devices and do scan
serverIntent = new Intent(this, DeviceListActivity.class);
try{
startActivityForResult(serverIntent, CONNECT_DEVICE);
}catch(ActivityNotFoundException activityNotFound){
Log.e(TAG, "Could not start DeviceListActivity(Insecure)");
}
return true;
}
return false;
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
switch(requestCode){
case CONNECT_DEVICE:
if(resultCode == Activity.RESULT_OK){
connectDevice(data);
}
break;
case DISCONNECT_DEVICE:
if(resultCode == Activity.RESULT_OK){
disconnectDevice();
}
break;
}
}
public Handler getHandler(){
return this.handler;
}
public BluetoothComm getBtComm(){
return this.btComm;
}
// The Handler that gets information back from the BluetoothChatService
private final Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
if(D) Log.d(TAG, "check message");
switch (msg.what) {
case MESSAGE_READ:
if(D) Log.d(TAG, "trying to read message");
byte[] readBuf = (byte[]) msg.obj;
// construct a string from the valid bytes in the buffer
String readMessage = new String(readBuf, 0, msg.arg1);
if(D) Log.d(TAG, "bytes: " + readBuf + " arg1: " + msg.arg1 + " Message: " + readMessage);
tv.append(readMessage);
break;
case MESSAGE_WRITE:
if(D) Log.d(TAG, "trying to send message");
String sendMessage = new String(String.valueOf(msg.obj));
}
}
};
}
Any other classes not listed I didn't believe needed to be, but if they are needed please let me know.
Any help is much appreciated
You're going to need to save the pointerId's of each point and compare them to the new Id's given with each MotionEvent. It's slightly tricky to explain, so I'll point you to this ADB Post that explains it much better than I could. Long story short? Multitouch can be tricksy, but it's not as bad as it looks at first glance.