I am needing to reformat an existing Android app to have a landscape version. It uses a TextWatcher to calculate a tip amount when numbers are entered into 2 EditTexts (bill amount & tip percent). It works by itself, but when I add a second layout, the TextWatcher stops working and doesn't calculate anything. It rotates and looks how I want it to, but doesn't function. My landscape layout has all the same parts as the portrait with the same EditText & TextView IDs. This is what I am using to change the layouts:
package com.example.tiporientation;
import androidx.appcompat.app.AppCompatActivity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.EditText;
import android.widget.TextView;
import java.text.NumberFormat;
public class MainActivity extends AppCompatActivity {
private TipCalculator tipCalc;
private NumberFormat money = NumberFormat.getCurrencyInstance();
private EditText billEditText;
private EditText tipEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tipCalc = new TipCalculator(.17f,100);
setContentView(R.layout.activity_main);
billEditText = findViewById(R.id.EDIT_BillAmount);
tipEditText = findViewById(R.id.EDIT_EnterTip);
//create inner-class for this... puts it at bottom of MainActivity
//TextChangeHandler is a "listener"
//attach it to our EDIT texts, so it is listening to changes in bill $ & tip %
TextChangeHandler tch = new TextChangeHandler();
billEditText.addTextChangedListener(tch);
tipEditText.addTextChangedListener(tch);
Configuration config = getResources().getConfiguration();
modifyLayout(config);
}
private void modifyLayout(Configuration newConfig) {
if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
setContentView(R.layout.activity_main_landscape); //we create new XML for this layout
else if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
setContentView(R.layout.activity_main);
}
public void onConfigurationChanged(Configuration newConfig) {
Log.w("MainActivity", "Inside onConfigurationChanged");
super.onConfigurationChanged(newConfig);
modifyLayout(newConfig); //if orientation changes again, send back to modifyLayout
}
public void calculate () {
//convert edit texts to a string ???
String billString = billEditText.getText().toString();
String tipString = tipEditText.getText().toString();
//2 text views from XML
TextView tipTextView = findViewById(R.id.TXT_TipTotal);
TextView totalTextView = findViewById(R.id.TXT_TotalAmount);
try {
//convert billString to float & tipString to int -- can't do math with strings
float billAmount = Float.parseFloat(billString);
int tipPercent = Integer.parseInt(tipString);
//update the model -- referencing TipCalculator class!
tipCalc.setBill(billAmount);
tipCalc.setTip(.01f * tipPercent);
//ask model to calculate
float tip = tipCalc.tipAmount();
float total = tipCalc.totalAmount();
//update view with formatted tip & total amount
tipTextView.setText(money.format(tip));
totalTextView.setText(money.format(total));
} catch(NumberFormatException nfe) { }
}
//create implements... select all 3 they are then stubbed out in the class
private class TextChangeHandler implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
calculate();
}
}
}
Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.tiporientation">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
With the onConfigurationChanged callback your activity is destroyed and re-created.
Values in your text fields can be stored in the savedInstanceState
and then retrieved in onCreate.
I am not entirely sure, but I think what happens here is:
Your onCreate
has already run, thus your TextWatcher
was attached to a layout you just destroyed with your setContentView call from modifyLayout
.
I still think you should let android manage this for you, but to solve the problem, I suggest:
remove the code from onCreate beginning with the first findViewById up to (and including) the .addTextWatcher lines
remove the other clutter with an additional modifyLayout call from your onCreate
put this code in a method, say connectWatchers
call this method from onCreate
call this method from modifyLayout
It should look like this:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tipCalc = new TipCalculator(.17f,100);
setContentView(R.layout.activity_main);
connectWatchers();
}
private void modifyLayout(Configuration newConfig) {
if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
setContentView(R.layout.activity_main_landscape); //we create new XML for this layout
else if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
setContentView(R.layout.activity_main);
connectWatchers();
}
public void onConfigurationChanged(Configuration newConfig) {
Log.w("MainActivity", "Inside onConfigurationChanged");
super.onConfigurationChanged(newConfig);
modifyLayout(newConfig); //if orientation changes again, send back to modifyLayout
}
private void connectWatchers() {
billEditText = findViewById(R.id.EDIT_BillAmount);
tipEditText = findViewById(R.id.EDIT_EnterTip);
TextChangeHandler tch = new TextChangeHandler();
billEditText.addTextChangedListener(tch);
tipEditText.addTextChangedListener(tch);
}