I'm having a hard time understanding how to update the view using data binding. I have a simple example I'm experimenting with, in which a toggle switch will make a button appear/disappear in the view.
Expected behavior:
The toggle button should toggle the "Add Key" button on/of (i.e. VISIBLE or GONE).
Actual behavior:
The "Add Key" button's visibility does not get updated. It only gets set once when the layout is loaded.
Here is the layout file:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="mainActivity"
type="tanager.sandbox.MainActivity" />
<variable
name="businessLogic"
type="tanager.sandbox.BusinessLogic" />
</data>
<RelativeLayout
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="tanager.sandbox.MainActivity">
<LinearLayout
android:id="#+id/linear_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
android:id="#+id/add_key_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="#{businessLogic.getVisible()}"
android:text="#string/add_key" />
</LinearLayout>
<ToggleButton
android:id="#+id/toggleButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="#id/linear_layout"
android:layout_weight="3"
android:text="ToggleButton"
android:onClick="#{() -> mainActivity.click()}"/>
</RelativeLayout>
</layout>
When the toggle button is pressed, it invokes a method on the main activity, which toggles a value in the business logic. I'm expecting the add_key_button to update its visibility automatically when the toggle button is pressed. Here is the MainActivity:
public class MainActivity extends AppCompatActivity {
private BusinessLogic _businessLogic;
#Override
protected void onCreate(Bundle savedInstanceState) {
_businessLogic = new BusinessLogic();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActivityMainBinding binding = DataBindingUtil.setContentView(this,
R.layout.activity_main);
binding.setMainActivity(this);
binding.setBusinessLogic(_businessLogic);
}
public void click() {
_businessLogic.toggleVisibility();
}
}
And here is the business logic class:
public class BusinessLogic extends BaseObservable {
private boolean visible;
public BusinessLogic(){
visible = true;
}
public void toggleVisibility(){
visible = !visible;
notifyPropertyChanged(BR.visible);
}
#Bindable
public int getVisible(){
return visible? View.VISIBLE:View.GONE;
}
}
I slightly refactored your code, this works
public void click() {
_businessLogic.toggleVisibility(binding.toggleButton.isChecked());
}
The BusinessLogic method now looks like
public class BusinessLogic extends BaseObservable {
private boolean isToggleOn;
public BusinessLogic() {
isToggleOn = false;
}
public void toggleVisibility(boolean toggleOn) {
isToggleOn = toggleOn;
notifyPropertyChanged(BR.toggleOn);
}
#Bindable
public boolean getToggleOn() {
return isToggleOn;
}
Then in the layout, you can set the visibility based on the getToggleOn method of the businessLogic class
<Button
android:id="#+id/add_key_button"
android:layout_width="match_parent"
android:visibility="#{businessLogic.toggleOn ? View.VISIBLE : View.GONE}"
android:layout_height="wrap_content"
android:text="Add key" />
Remember to import the view class in your layout
<data>
<import type="android.view.View"/>
<variable
name="mainActivity"
type="stackoverflow.MainActivity" />
Related
I am trying to switch between two WebViews in my layout with Android Java. The two WebViews load different urls when the oncreate method is called. The only problem am having is that the first webview shows up with the site content when oncreate is called but when I click the button that is supposed to show the next webview, it removes the current webview from view and then shows white screen. Help me resolve that, below is my layout file and the android java code behind file
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ViewSwitcher
android:layout_height="wrap_content"
android:id="#+id/viewSwitcher1"
android:layout_width="wrap_content">
<LinearLayout
android:id="#+id/view1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.webkit.WebView
android:id="#+id/webView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<LinearLayout
android:id="#+id/view2"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.webkit.WebView
android:id="#+id/webView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</ViewSwitcher>
<Button
android:id="#+id/button1"
android:text="Show Next"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</ScrollView>
and Android Java code for the behavior
public class MainActivity : AppCompatActivity
{
ViewSwitcher viewSwitcher;
private Button next;
protected WebView mywebview,webview2;
protected int mycount=0;
protected View view1, view2;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.activity_main);
viewSwitcher = FindViewById<ViewSwitcher>(Resource.Id.viewSwitcher1);
next = FindViewById<Button>(Resource.Id.button1);
mywebview = FindViewById<WebView>(Resource.Id.webView1);
webview2 = FindViewById<WebView>(Resource.Id.webView2);
view1 = FindViewById<View>(Resource.Id.view1);
view2 = FindViewById<View>(Resource.Id.view2);
WebViewClient client = mywebview.WebViewClient;
mywebview.LoadUrl("https://www.upwork.com");
webview2.LoadUrl("https:www.sololearn.com");
//set the in and out animation for the viewswitcher
viewSwitcher.SetOutAnimation(this, Resource.Animation.abc_slide_out_top);
viewSwitcher.SetInAnimation(this, Resource.Animation.design_bottom_sheet_slide_in);
next.Click += Next_Click;
//hide the action bar
}
private void Next_Click(object sender, System.EventArgs e)
{
if (viewSwitcher.CurrentView != view1)
{
viewSwitcher.ShowPrevious();
}else if(viewSwitcher.CurrentView != view2)
{
viewSwitcher.ShowNext();
}
}
}
Tries to add actions to the onClick() event button (with DataBinding). I do everything like in the materials found on the web, but it doesn't work. The onSetLocationClick() method is not called.
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="location"
type="com.example.mytracker.model.Location" />
<variable
name="activity"
type="com.example.mytracker.MainActivity" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:paddingRight="10dp"
android:paddingBottom="10dp"
tools:context=".MainActivity">
<TextView
android:id="#+id/tvLongitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:text='#{location.longitudeText}'
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/bSetLocation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:onClick="#{() -> activity.onSetLocationClick()}"
android:text="SET LOCATION"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="#id/tvLongitude" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
MainActivity.java:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
Location location = new Location();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setLocation(location);
TextView tvLabel = binding.tvLabel;
location.longitude = 1.1;
}
public void onSetLocationClick() {
location.longitude=99;
}
}
In build.gradle file i was added:
buildFeatures {
dataBinding true
}
I found a few similar threads but found no solution in any of them.
I will add that after changing the declaration
android:onClick="#{() -> activity.onSetLocationClick()}"
to
android:onClick="onSetLocationClick"
and declaration method from
public void onSetLocationClick()
to
public void onSetLocationClick(View view)
the code works fine.
However, I would like to use:
android:onClick="#{() -> activity.onSetLocationClick()}"
You have to call setActivity() on your binding to actually initialize your activity variable.
setContentView(R.layout.activity_main);
Remove this line in your code and check it,
I have a login form validation mechanism. There are two TextInputEditTexts for email and password, both being attached to respective onFocusChangeListeners, DataBinding is used to set those, but DataBinding returns wrong listeners.
Here is the form:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="loginViewModel"
type="com.example.increaseyouriq.ui.forms.LoginFormViewModel" />
</data>
<LinearLayout
android:id="#+id/ll_login"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="#style/parent.contentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="26dp">
<com.google.android.material.card.MaterialCardView
style="#style/cardOutline"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
style="#style/viewParent.headerText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/login_title" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
style="#style/textInputStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="#string/enter_email"
error="#{loginViewModel.loginForm.emailError}"
app:errorEnabled="#{loginViewModel.loginForm.isEmailError()}"
app:endIconMode="clear_text">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/et_login_email"
style="#style/textInputEditext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
onFocus="#{loginViewModel.onFocusEmailChangeListener}"
android:text="#={loginViewModel.loginFields.email}"
android:inputType="textNoSuggestions|textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="#style/textInputStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/enter_password"
error="#{loginViewModel.loginForm.passwordError}"
app:errorEnabled="#{loginViewModel.loginForm.isPasswordError()}"
app:endIconMode="password_toggle">
<com.google.android.material.textfield.TextInputEditText
style="#style/textInputEditext"
android:id="#+id/et_login_password"
onFocus="#{loginViewModel.onFocusPasswordChangeListener}"
android:text="#={loginViewModel.loginFields.password}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</layout>
Here is my LoginFormViewModel.java:
public class LoginFormViewModel extends ViewModel {
private static final String LOG_TAG = "LoginFormViewModel";
private View.OnFocusChangeListener onFocusEmail;
private View.OnFocusChangeListener onFocusPassword;
public void init() {
loginForm = new LoginForm();
onFocusPassword = (v, hasFocus) -> {
TextInputEditText et = (TextInputEditText) v;
MyUtilsApp.showLog(LOG_TAG,
String.format("%s password listener attached to :%d", String.valueOf(et.getText()), et.getId()));
if (et.getText().length() > 0 && !hasFocus) {
loginForm.isPasswordValid(true);
}
};
onFocusEmail = (v, hasFocus) -> {
TextInputEditText et = (TextInputEditText) v;
MyUtilsApp.showLog(LOG_TAG, "email listener attached");
if (et.getText().length() > 0 && !hasFocus) {
loginForm.isEmailValid(true);
}
};
}
public View.OnFocusChangeListener getOnFocusEmailChangeListener() {
return onFocusEmail;
}
public View.OnFocusChangeListener getOnFocusPasswordChangeListener() {
return onFocusPassword;
}
#BindingAdapter("onFocus")
public static void bindFocusChange(TextInputEditText editText, View.OnFocusChangeListener onFocusChangeListener) {
if (editText.getOnFocusChangeListener() == null) {
editText.setOnFocusChangeListener(onFocusChangeListener);
}
}
}
Here is my LoginActivity.java:
public class LoginActivity extends AppCompatActivity {
private static final String LOG_TAG = "LoginActivity";
ActivityLoginBinding binding;
private LoginRegisterViewModel loginRegisterViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this,R.layout.activity_login);
setupBindings(savedInstanceState);
}
private void setupBindings(Bundle savedInstanceState) {
LoginFormViewModel loginFormViewModel = new ViewModelProvider(this).get(LoginFormViewModel.class);
if(savedInstanceState==null)
loginFormViewModel.init();
binding.setLoginViewModel(loginFormViewModel);
binding.setLifecycleOwner(this);
loginFormViewModel.getLoginFields().observe(this, loginFields -> {
Toast.makeText(LoginActivity.this,
"Email " + loginFields.getEmail() + ", Password " + loginFields.getPassword(),
Toast.LENGTH_SHORT).show();
});
binding.inLayoutLogin.btnLoginLogin.setOnClickListener(view -> {
binding.executePendingBindings();
loginFormViewModel.getLoginForm().onClick();
});
}
}
As evident, I have set both listeners to be of onFocusEmail kind, just because onFocusPassword is being attached to both TextInputEditTexts during runtime.
Here are logs:
com.example.increaseyouriq E/LoginFormViewModel: password listener attached to :2131296499
com.example.increaseyouriq E/LoginFormViewModel: ttf password listener attached to :2131296499
onFocusEmail is nowhere to be seen, it has been attached during runtime.
for more details on code see here
I did some experiments on your code and I believe I found out the underlying reason.
Problem:
Neither of your listeners are attached to your email field (TextInputEditText "et_login_email").
Reason:
The reason is that you have a null check here;
#BindingAdapter("onFocus")
public static void bindFocusChange(TextInputEditText editText, View.OnFocusChangeListener onFocusChangeListener) {
if (editText.getOnFocusChangeListener() == null) {
editText.setOnFocusChangeListener(onFocusChangeListener);
}
}
And your email field has an onFocusChangeListener already which is set by "app:endIconMode="clear_text"" attribute.
<com.google.android.material.textfield.TextInputLayout
...
android:hint="#string/enter_email"
..."
app:endIconMode="clear_text">
You cannot set a listener to the email field because its listener is not null from the begining and the current method (bindFocusChange()) only sets it when it is null.
Solution 1:
Remove your null check when setting the onFocusChangeListeners.
Solution 2:
Utilize data binding library's automatic method selection with less code.
Change your custom attribute's name "onFocus" to "onFocusChangeListener". As the TextInputEditText fields have a matching setter method (setOnFocusChangeListener) for this name, the library can handle this automatically and you can remove your "bindFocusChange(...)" method completely:
as my title says I'm here with a problem in Android studio (JAVA) with my app, how to call class as an Activity when class has extends LinearLayout?
My class is:
public class CustomCalendar extends LinearLayout {
My code where I try to call it:
Intent customCalendar = new Intent(MainActivity.this, CustomCalendarActivity.class);
startActivity(customCalendar);
I tried to make this:
public class CustomCalendarActivity extends AppCompatActivity {
CustomCalendar customCalendar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
customCalendar = (CustomCalendar) findViewById(R.id.kalendoriaus_virsus);
customCalendar.SetUpCalendar();
}
}
My crash is this:
java.lang.NullPointerException: Attempt to invoke virtual method 'void com.example.kalendorius.CustomCalendar.SetUpCalendar()' on a null object reference
at com.example.kalendorius.MainActivity$2.onClick(MainActivity.java:75)
75 Line is:
startActivity(customCalendar);
How I setup my calendar:
My kalendorius.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/kalendoriaus_virsus"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/soft_blue_green"
android:orientation="horizontal"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<ImageButton
android:id="#+id/atgalBtn"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_margin="10dp"
android:background="#drawable/back" />
<TextView
android:id="#+id/dabartineData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_weight="3"
android:gravity="center"
android:text="Data"
android:textColor="#ffffff"
android:textSize="18sp"
android:textStyle="bold" />
That's just a bit code where I use that kalendorius_virsus.
DONE. I figure out what was the problem. I just forgot to generate for database constructor:D Thank you for everyone who tried to help.
The problem is
customCalendar = (CustomCalendar) findViewById(R.id.kalendoriaus_virsus);
The groupView LinearLayout with id kalendorias_virsus in the kalendorias.xml is not a CustomCalendar so always return null.
You need to understand how to properly user a costum view
basic example CustomView ->
public class CustomView extends LinearLayout {
public CustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
View view = LayoutInflater.from(getContext()).inflate(
R.layout.costum, null);
this.addView(view);
}
public void setCustomText(String text){
TextView textview = (TextView) findViewById(R.id.textViewId);
textview.setText(text);
}
}
layout custom.xml ->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/textViewId"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="#string/app_name"
android:gravity="center">
</TextView>
</LinearLayout>
activity.xml->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<com.example.myapplication.CustomView
android:id="#+id/customViewId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
MainActivity class->
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CustomView customView = findViewById(R.id.customViewId);
customView.setCustomText("Welcome ");
}
}
You have many errors in your code. We still need more information to determine why you are getting a NullPointerException (NPE) in the MainActivity.onClick() method. However, this will for sure crash:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
customCalendar = (CustomCalendar) findViewById(R.id.kalendoriaus_virsus);
customCalendar.SetUpCalendar();
}
because customCalendar will be null because you have not called setContentView() before calling findBiewById().
You need to add
setContentView(R.layout.XXXXXX);
before you call findViewById(). The XXXXXX above must be the name of your layout XML file (without the .xml file extension).
I have the following in my layout file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:id="#+id/topNav"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/stop_surfing"/>
<TextView
style="#style/Counter"
/>
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>
</LinearLayout>
When I run the method surf(), I was assuming that my linear layout would show, but instead nothing happens. Do I need to do something else to refresh the activity or something?
Here is the main activity:
public class MainActivity extends Activity{
public void onCreate(Bundle savedInstanceState){
// Snipped code //
WebView webView = (WebView)findViewById(R.id.webview);
webView.addJavascriptInterface(new JavaScriptBinder(this), "$js");
// Snipped code //
}
}
And here is the secondary class:
public class JavaScriptBinder{
Activity context;
JavaScriptBinder(Activity context){
this.context = context;
}
public void surf(String memberId){
// Snipped code //
LinearLayout top = (LinearLayout)context.findViewById(R.id.topNav);
top.setVisibility(View.VISIBLE);
}
}
surf() is called from a javascript file loaded in the webview:
function startSurfing(){
var users = document.getElementsByClassName("user");
for(var i = 0; i < users.length; i++){
users[i].addEventListener("click", function(e){
e.stopPropagation();
with(document.getElementById("black-overlay").style){
display = "block";
backgroundColor = "rgba(0,0,0,0.7)";
}
var userId = this.dataset.id;
$js.surf(userId);
}, false);
}
}
The documentation states that the code is run in a different Thread, so of course, you can't update your UI from this method just like this, see here:
http://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object, java.lang.String)
JavaScript interacts with Java object on a private, background thread of this WebView. Care is therefore required to maintain thread safety.
So what you need to do, is to run that code in the UI Thread.
Also, you need to annotate your method with #JavascriptInterface, for the method to be callable for Android 4.2 and above.
Try with this:
#JavascriptInterface
public void surf(String memberId){
// Snipped code //
final LinearLayout top = (LinearLayout)context.findViewById(R.id.topNav);
context.runOnUiThread(new Runnable(){
#Override
public void run(){
top.setVisibility(View.VISIBLE);
}
});
}