I am able to put data into Firebase database, but when I want to retrieve it and display it to ListView, it only displays blank screen.
BaseActivity class:
public class BaseActivity extends AppCompatActivity {
#Bean
OttoBus bus;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bus.register(this);
}
#Override
protected void onDestroy() {
super.onDestroy();
bus.unregister(this);
}
}
HomeActivity class:
#OptionsMenu(R.menu.signout)
#EActivity(R.layout.activity_home)
public class HomeActivity extends BaseActivity {
private static final int LOGIN_REQUEST_CODE = 42;
#ViewById
ListView listView;
#Bean
ConversationAdapter conversationAdapter;
#Bean
UserDao userDao;
#AfterViews
void init() {
// if no user is logged in
if (FirebaseAuth.getInstance().getCurrentUser() == null) {
LoginActivity_.intent(this).startForResult(LOGIN_REQUEST_CODE);
} else {
userDao.init();
}
listView.setAdapter(conversationAdapter);
}
#OnActivityResult(value = LOGIN_REQUEST_CODE)
void loginSucceeded(int resultCode) {
if (resultCode != RESULT_OK) {
return;
}
userDao.init();
conversationAdapter.resetConversationFlow();
}
#Subscribe
public void usersLoaded(UsersLoadedEvent event) {
final FirebaseUser firebaseUser = FirebaseAuth
.getInstance().getCurrentUser();
if (userDao.userExists(firebaseUser.getUid())) {
userDao.setCurrentUser(userDao.getUserById(firebaseUser.getUid()));
} else {
final User user = new User(firebaseUser.getUid(),
firebaseUser.getDisplayName(),
firebaseUser.getPhotoUrl().toString());
userDao.write(user);
userDao.setCurrentUser(user);
}
}
#ItemClick
void listViewItemClicked(Conversation conversation) {
ConversationActivity_.intent(this)
.conversation(conversation)
.start();
}
/**
* When option item with id signOut is clicked.
*/
#OptionsItem
void signOut() {
FirebaseAuth.getInstance().signOut();
// restart this activity after user is logged out because if there is no user we will start
// login activity
final Intent intent = getIntent();
finish();
startActivity(intent);
}
/**
* Called when button with id=fab is clicked.
*/
#Click
void fab() {
CreateConversationActivity_.intent(this).start();
}
}
activity_home.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.nemus.execomchatworkshop.activity.HomeActivity">
<ListView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_margin="#dimen/large_margin"
android:src="#drawable/ic_add_black_24dp" />
</RelativeLayout>
ConversationItemView class:
#EViewGroup(R.layout.item_view_conversation)
public class ConversationItemView extends LinearLayout {
#ViewById
TextView title;
public ConversationItemView(Context context) {
super(context);
}
public void bind(Conversation conversation) {
title.setText(conversation.getTitle());
}
}
item_view_conversation.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="#+id/conversationPhoto"
android:layout_width="#dimen/image_large_size"
android:layout_height="#dimen/image_large_size"
android:layout_margin="#dimen/medium_margin" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="#dimen/large_margin"
android:orientation="vertical">
<TextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textColor="#color/primary_text"
android:textSize="#dimen/large_text" />
<TextView
android:id="#+id/conversationPreview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textColor="#color/secondary_text"
android:textSize="#dimen/medium_text" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
ConversationDao class
#EBean(scope = EBean.Scope.Singleton)
public class ConversationDao {
static final String CONVERSATION_TAG = "conversations";
private FirebaseDatabase database = FirebaseDatabase.getInstance();
private List<Conversation> conversations = new ArrayList<>();
private Map<String, Conversation> conversationMap = new HashMap<>();
#Bean
OttoBus bus;
/**
* After this class is injected call this method.
*/
#AfterInject
public void init() {
database.getReference(CONVERSATION_TAG).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
conversationMap = dataSnapshot.getValue(
new GenericTypeIndicator<Map<String, Conversation>>() {
});
publish();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
public void write(Conversation conversation) {
final DatabaseReference databaseReference =
database.getReference(CONVERSATION_TAG).push();
conversation.setId(databaseReference.getKey()); // set unique key to our conversation
databaseReference.setValue(conversation); // push conversation to database
}
public List<Conversation> getConversations() {
return conversations;
}
private void publish() {
conversations.clear();
if (conversationMap != null) {
conversations.addAll(conversationMap.values());
}
// post to event bus
bus.post(new ConversationsUpdateEvent());
}
ConversationAdapter class:
#EBean
public class ConversationAdapter extends BaseAdapter {
private List<Conversation> conversations = new ArrayList<>();
#RootContext
Context context;
#Bean
ConversationDao conversationDao;
#Bean
OttoBus bus;
/**
* This method is called after this class is injected.
*/
#AfterInject
void init() {
bus.register(this);
}
public void resetConversationFlow() {
conversationDao.init();
}
#Override
public int getCount() {
return conversations.size();
}
#Override
public Conversation getItem(int position) {
return conversations.get(position);
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final ConversationItemView conversationItemView;
if (convertView == null) { // if view item is not created
conversationItemView = ConversationItemView_.build(context);
} else { // if view item was already created
conversationItemView = (ConversationItemView) convertView;
}
// bind data to view
conversationItemView.bind(getItem(position));
return conversationItemView;
}
private void setConversations(List<Conversation> conversations) {
this.conversations = conversations;
// notify that data set changed so that the list is refreshed
notifyDataSetChanged();
}
#Subscribe
public void conversationsUpdated(ConversationsUpdateEvent event) {
setConversations(conversationDao.getConversations());
}
}
So, what am I missing here. Why is not displaying conversation title into ListView?
It's difficult to follow allow with all this code at once. You are more likely to get answers if you ask one small question, than one large one. This is what I would recommend for you to figure out how to solve this issue:
Get familiar with the Debugger. It is essential and once you learn it you will become much better at solving any issues and coding in general.
Setup breakpoints to stop after you've successfully retrieved your data. Then confirm that you actually received the data and it is currently stored in the variable you wish to use.
Now that you've confirmed that your data exists, make sure you're setting up the list view correctly.
Firstly, I would recommend you start out with RecylerView, as that is the much newer standard and has much more helpful tutorials online. They are fundamently the same thing but RecylerView is better. Then I would continue to use the debugger to check that my data is being passed in to the RecylerViewAdapter correctly, and that when the data is bounded, it is pointing it to the correct views.
Here is a good tutorial for using the Android studio debugger.
Here is a good tutorial by google for adding lists(Using recylerview)
Related
When I try to set the onClick method in my Google's SignInButton:
android:onClick="#{() -> viewModel.onGoogleLoginClick()}"
I always get this error:
Found data binding errors.
****/ data binding error ****msg:Cannot find the proper callback class for android:onClick. Tried android.view.View but it has 0 abstract
methods, should have 1 abstract methods.
file:/Users/user/Android/project/app/src/main/res/layout/activity_login.xml loc:53:31 - 53:66 ****\ data binding error ****
Here is my code:
activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".ui.login.LoginActivity">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="com.example.myapp.ui.login.LoginViewModel" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="#dimen/default_layout_padding">
<EditText
android:id="#+id/login_name_editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/login_username_hint"
android:inputType="text"
android:text="#{viewModel.mEmail}" />
<EditText
android:id="#+id/login_pass_editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/login_name_editText"
android:hint="#string/login_password_hint"
android:inputType="numberPassword"
android:text="#{viewModel.mPassword}" />
<Button
android:id="#+id/login_login_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/login_pass_editText"
android:onClick="#{() -> viewModel.onServerLoginClick()}"
android:text="#string/login_login_button_text"
android:textAllCaps="true" />
<com.google.android.gms.common.SignInButton
android:id="#+id/login_google_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/login_login_button"
android:onClick="#{() -> viewModel.onGoogleLoginClick()}"/>
</RelativeLayout>
</layout>
LoginViewModel.class
public class LoginViewModel extends BaseViewModel<LoginNavigator> implements
GoogleApiClient.OnConnectionFailedListener, OnCompleteListener<GoogleSignInAccount>,
GoogleApiClient.ConnectionCallbacks {
private static final String LOG_TAG = LoginViewModel.class.getSimpleName();
public String mEmail;
public String mPassword;
public LoginViewModel(DataManager dataHelper, SchedulerProvider schedulerProviderHelper) {
super(dataHelper, schedulerProviderHelper);
}
public void onServerLoginClick() {
if (CommonUtils.loginDataIsCorrect(mEmail, mPassword)) {
doServerLogin(mEmail, mPassword);
} else {
getNavigator().handleError();
}
}
public void onGoogleLoginClick() {
getNavigator().googleLogin();
}
// Server
private void doServerLogin(String name, String pass) {
...
}
// Google
protected void doGoogleLogin(FragmentActivity fragmentActivity, Context context) {
...
}
...
}
LoginActivity.class
public class LoginActivity extends BaseActivity<ActivityLoginBinding, LoginViewModel> implements LoginNavigator {
private static final int REQUEST_CODE_REGISTER = 0;
private static final int REQUEST_CODE_GOOGLE_SIGN_IN = 1;
#BindString(R.string.login_data_missing_message)
String mDataMissingMessage;
#Inject
LoginViewModel mLoginViewModel;
private ActivityLoginBinding mActivityLoginBinding;
public static Intent newIntent(Context context) {
return new Intent(context, LoginActivity.class);
}
#Override
public int getBindingVariable() {
return BR.viewModel;
}
#Override
public int getLayoutId() {
return R.layout.activity_login;
}
#Override
public LoginViewModel getViewModel() {
return mLoginViewModel;
}
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mActivityLoginBinding = getViewDataBinding();
mLoginViewModel.setNavigator(this);
mActivityLoginBinding.loginGoogleButton.setSize(SignInButton.SIZE_WIDE);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_GOOGLE_SIGN_IN:
mLoginViewModel.handleGoogleSignInResult(data);
break;
}
super.onActivityResult(requestCode, resultCode, data);
}
#Override
public void googleLogin() {
mLoginViewModel.doGoogleLogin(this, this);
}
#Override
public void showGoogleForm(GoogleApiClient googleApiClient) {
Intent googleIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
startActivityForResult(googleIntent, REQUEST_CODE_GOOGLE_SIGN_IN);
}
...
}
And the BaseActivity.class, where I bind view and data for each Activity:
public abstract class BaseActivity<T extends ViewDataBinding, V extends BaseViewModel> extends AppCompatActivity {
private T mViewDataBinding;
private V mViewModel;
public abstract int getBindingVariable();
#LayoutRes
public abstract int getLayoutId();
public T getViewDataBinding() {
return mViewDataBinding;
}
public abstract V getViewModel();
public void performDependencyInjection() {
AndroidInjection.inject(this);
}
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
performDependencyInjection();
super.onCreate(savedInstanceState);
performDataBinding();
}
#Override
protected void onResume() {
super.onResume();
}
private void performDataBinding() {
mViewDataBinding = DataBindingUtil.setContentView(this, getLayoutId());
this.mViewModel = mViewModel == null ? getViewModel() : mViewModel;
mViewDataBinding.setVariable(getBindingVariable(), mViewModel);
mViewDataBinding.executePendingBindings();
}
}
Does anyone know why this error? Because SignInButton implements OnClickListener. I have tried Invalidate Caches / Restart and deleting .gradle and .idea folders but is still not working.
Luckily, we've got #BindingAdapter to solve issues similar to this. Here's an example in Kotlin:
BindingAdapters.kt
#BindingAdapter("android:onClick")
fun bindSignInClick(button: SignInButton, method: () -> Unit) {
button.setOnClickListener { method.invoke() }
}
layout.xml
<com.google.android.gms.common.SignInButton
...
android:onClick="#{() -> viewModel.onSignInClick()}" />
It's a interesting question, since SignInButton extends View, but the doc states explicitly to register a listener with setOnClickListener(OnClickListener) in the class and not in the xml. Databinding wraps up the lamda expression as a listener (you can see that in the auto-generated data binding class) and probably it doesn't stick with the listener, which SignInButton is expecting. E.g. if you try to pass a View.OnClickListener variable via xml, you shouldn't get that compile error, but you probably also won't be able to receive your click events (like it's stated in the doc).
I'm trying to use MVVM with databinding .. Where should I write click event (Viewmodel Or Activity)
Examples are welcome...
You can write your click events on either. Personally, my preference depends on what I want to achieve and what I need to achieve it.
Click event in ViewModel
Define your ViewModel. Communicate with your activity via a Callback.
public class MyViewModel extends ViewModel{
private MyCustomCallback callback;
...
public void doOnClick(MyCustomCallback mCallback){
boolean isSuccessful = doMyAction.execute();
if (isSuccessful){
mCallback.actionIsSuccessful();
} else {
mCallback.actionFailed();
}
}
...
public void setCallback(callback){
this.callback = callback;
}
public MyCustomCallback getCallback(){
return this.callback;
}
...
public interface MyCustomCallback{
void actionIsSuccessful();
void actionFailed();
}
}
Implement this callback in your activity:
public class MyActivity extends AppCompatActivity{
...
private MyCustomCallback callback;
#Override
protected void onCreate(Bundle savedInstanceState){
...
callback = new MyViewModel.MyCustomCallback{
#Override
public void actionIsSuccessful(){
//do something when action is successful
}
#Override
public void actionFailed(){
//do something when action fails
}
}
viewModel.setCallback(callback);
}
}
Pass your ViewModel as a variable to your XML. Then do this:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/button_background"
android:layout_margin="20dp"
tools:text="CLICK ME!!!"
android:textColor="#android:color/white"
android:onClick="#{() -> ViewModel.doOnClick(ViewModel.callback)}"/>
Click events in Activity
public class MyActivity extends AppCompatActivity{
...
private MyCustomCallback callback;
#Override
protected void onCreate(Bundle savedInstanceState){
//initialize your binding
...
binding.setClickHandler(new MyActivityClickHandler());
}
public class MyActivityClickHandler{
public void onClickMeClicked(View view){
//do something
}
}
}
Then in your XML:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/button_background"
android:layout_margin="20dp"
android:text="#string/verify"
android:textColor="#android:color/white"
android:onClick="#{ClickHandler::onClickMeClicked}"/>
For more information check the official doc here
I'm a bit confused about how data binding should work when using the new Architecture Components.
let's say I have a simple Activity with a list, a ProgressBar and a TextView. the Activity should be responsible for controlling the state of all the views, but the ViewModel should hold the data and the logic.
For example, my Activity now looks like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
listViewModel = ViewModelProviders.of(this).get(ListViewModel.class);
binding.setViewModel(listViewModel);
list = findViewById(R.id.games_list);
listViewModel.getList().observeForever(new Observer<List<Game>>() {
#Override
public void onChanged(#Nullable List<Game> items) {
setUpList(items);
}
});
listViewModel.loadGames();
}
private void setUpList(List<Game> items){
list.setLayoutManager(new LinearLayoutManager(this));
GameAdapter adapter = new GameAdapter();
adapter.setList(items);
list.setAdapter(adapter);
}
and the ViewModel it's only responsible for loading the data and notify the Activity when the list is ready so it can prepare the Adapter and show the data:
public int progressVisibility = View.VISIBLE;
private MutableLiveData<List<Game>> list;
public void loadGames(){
Retrofit retrofit = GamesAPI.create();
GameService service = retrofit.create(GameService.class);
Call<GamesResponse> call = service.fetchGames();
call.enqueue(this);
}
#Override
public void onResponse(Call<GamesResponse> call, Response<GamesResponse> response) {
if(response.body().response.equals("success")){
setList(response.body().data);
}
}
#Override
public void onFailure(Call<GamesResponse> call, Throwable t) {
}
public MutableLiveData<List<Game>> getList() {
if(list == null)
list = new MutableLiveData<>();
if(list.getValue() == null)
list.setValue(new ArrayList<Game>());
return list;
}
public void setList(List<Game> list) {
this.list.postValue(list);
}
My question is: which is the correct way to show/hide the list, progressbar and error text?
should I add an Integer for each View in the ViewModel making it control the views and using it like:
<TextView
android:id="#+id/main_list_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{viewModel.error}"
android:visibility="#{viewModel.errorVisibility}" />
or should the ViewModel instantiate a LiveData object for each property:
private MutableLiveData<Integer> progressVisibility = new MutableLiveData<>();
private MutableLiveData<Integer> listVisibility = new MutableLiveData<>();
private MutableLiveData<Integer> errorVisibility = new MutableLiveData<>();
update their value when needed and make the Activity observe their value?
viewModel.getProgressVisibility().observeForever(new Observer<Integer>() {
#Override
public void onChanged(#Nullable Integer visibility) {
progress.setVisibility(visibility);
}
});
viewModel.getListVisibility().observeForever(new Observer<Integer>() {
#Override
public void onChanged(#Nullable Integer visibility) {
list.setVisibility(visibility);
}
});
viewModel.getErrorVisibility().observeForever(new Observer<Integer>() {
#Override
public void onChanged(#Nullable Integer visibility) {
error.setVisibility(visibility);
}
});
I'm really struggling to understand that. If someone can clarify that, it would be great.
Thanks
Here are simple steps:
public class MainViewModel extends ViewModel {
MutableLiveData<ArrayList<Game>> gamesLiveData = new MutableLiveData<>();
// ObservableBoolean or ObservableField are classes from
// databinding library (android.databinding.ObservableBoolean)
public ObservableBoolean progressVisibile = new ObservableBoolean();
public ObservableBoolean listVisibile = new ObservableBoolean();
public ObservableBoolean errorVisibile = new ObservableBoolean();
public ObservableField<String> error = new ObservableField<String>();
// ...
// For example we want to change list and progress visibility
// We should just change ObservableBoolean property
// databinding knows how to bind view to changed of field
public void loadGames(){
GamesAPI.create().create(GameService.class)
.fetchGames().enqueue(this);
listVisibile.set(false);
progressVisibile.set(true);
}
#Override
public void onResponse(Call<GamesResponse> call, Response<GamesResponse> response) {
if(response.body().response.equals("success")){
gamesLiveData.setValue(response.body().data);
listVisibile.set(true);
progressVisibile.set(false);
}
}
}
And then
<data>
<import type="android.view.View"/>
<variable
name="viewModel"
type="MainViewModel"/>
</data>
...
<ProgressBar
android:layout_width="32dp"
android:layout_height="32dp"
android:visibility="#{viewModel.progressVisibile ? View.VISIBLE : View.GONE}"/>
<ListView
android:layout_width="32dp"
android:layout_height="32dp"
android:visibility="#{viewModel.listVisibile ? View.VISIBLE : View.GONE}"/>
<TextView
android:id="#+id/main_list_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{viewModel.error}"
android:visibility="#{viewModel.errorVisibile ? View.VISIBLE : View.GONE}"/>
Also notice that it's your choice to make view observe
ObservableBoolean : false / true
// or
ObservableInt : View.VISIBLE / View.INVISIBLE / View.GONE
but ObservableBoolean is better for ViewModel testing.
Also you should observe LiveData considering lifecycle:
#Override
protected void onCreate(Bundle savedInstanceState) {
...
listViewModel.getList().observe((LifecycleOwner) this, new Observer<List<Game>>() {
#Override
public void onChanged(#Nullable List<Game> items) {
setUpList(items);
}
});
}
Here are simple steps to achieve your point.
First, have your ViewModel expose a LiveData object, and you can start the LiveData with an empty value.
private MutableLiveData<List<Game>> list = new MutableLiveData<>();
public MutableLiveData<List<Game>> getList() {
return list;
}
Second, have your view (activity/fragment) observe that LiveData and change UI accordingly.
listViewModel = ViewModelProviders.of(this).get(ListViewModel.class);
listViewModel.data.observe(this, new Observer<List<Game>>() {
#Override
public void onChanged(#Nullable final List<Game> games) {
setUpList(games);
}
});
Here it is important that you use the observe(LifecycleOwner, Observer) variant so that your observer do NOT receive events after that LifecycleOwner is no longer active, basically, that means that when your activity of fragment is no longer active, you won't leak that listener.
Third, as a result of data becoming available you need to update your LiveData object.
#Override
public void onResponse(Call<GamesResponse> call, Response<GamesResponse> response) {
if(response.body().response.equals("success")){
List<Game> newGames = response.body().data; // Assuming this is a list
list.setValue(newGames); // Update your LiveData object by calling setValue
}
}
By calling setValue() on your LiveData, this will cause onChanged on your view's listener to be called and your UI should be updated automatically.
I'm using the following dependencies in build.gradle:
compile 'com.google.firebase:firebase-database:11.4.2'
compile 'com.firebaseui:firebase-ui-database:3.1.0'
compile 'com.firebaseui:firebase-ui-auth:3.1.0'
My goal is to populate a Listview with data from a Firebase Database.
I'm following the guide on Github Firebase
The following method should bind the data to a listview:
private void showMessages() {
Query query = FirebaseDatabase.getInstance()
.getReference("notfication/unread/" + userMailAddress.replace(".", ","))
.orderByKey();
FirebaseListOptions<Message> options = new FirebaseListOptions.Builder<Message>()
.setLayout(R.layout.fbmessage_listitem)//Note: The guide doesn't mention this method, without it an exception is thrown that the layout has to be set.
.setQuery(query, Message.class)
.build();
FirebaseListAdapter<Message> adapter = new FirebaseListAdapter<Message>(options) {
#Override
protected void populateView(View v, Message model, int position) {
TextView tvMessage = v.findViewById(R.id.tv_message);
tvMessage.setText(model.getDateTimeCreated());
}
};
ListView readMessageList = findViewById(R.id.lvReadMessageList);
readMessageList.setAdapter(adapter);
}
This is the layout that contains the TextViews.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.6"
android:orientation="vertical">
<TextView
android:id="#+id/tv_created_on"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:gravity="start"
android:paddingStart="5dp"
android:paddingEnd="0dp"
android:textColor="#android:color/black" />
<TextView
android:id="#+id/tv_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:gravity="start"
android:paddingStart="5dp"
android:paddingEnd="0dp"
android:textColor="#android:color/black" />
</LinearLayout>
<ImageView
android:id="#+id/iv_message_read"
android:layout_width="200dp"
android:layout_height="30dp"
android:layout_weight="0.2"
android:contentDescription="#string/message_read"
android:elevation="2dp"
app:srcCompat="#drawable/android_ok_sign" />
<ImageView
android:id="#+id/iv_delete"
android:layout_width="200dp"
android:layout_height="30dp"
android:layout_weight="0.2"
android:contentDescription="#string/abc_delete"
android:elevation="2dp"
android:src="#drawable/ic_delete_red_48dp" />
The model:
public class Message {
private String message;
private String sender;
private String receiver;
private long timestamp;
private boolean isRead;
private String userMailAddress;
public Message() {
}
public Message(String message, String sender, String receiver, long timestamp, boolean isRead) {
this.message = message;
this.sender = sender;
this.receiver = receiver;
this.timestamp = timestamp;
this.isRead = isRead;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public boolean isRead() {
return isRead;
}
public void setRead(boolean read) {
isRead = read;
}
public String getUserMailAddress() {
return userMailAddress;
}
public void setUserMailAddress(String userMailAddress) {
this.userMailAddress = userMailAddress;
}
public String getDateTimeCreated() {
Calendar calendar = Calendar.getInstance(Locale.getDefault());
calendar.setTimeInMillis(timestamp);
String date = DateFormat.format("dd-MM-yyyy", calendar).toString();
String time = DateFormat.format("HH:mm", calendar).toString();
return date + "\r\n" + time;
}
}
No matter what i try, set a breakpoint or Log a TAG, the method populateView to bind the data from the model to the textfields isn't called.
I use the same layouts in the previous FirebaseUI (still in my production app) and everything is working fine there.
Does anyone know why the override method populateView isn't called?
I had the same problem, fixed it with:
#Override
protected void onStart() {
super.onStart();
mAdapter.startListening();
}
And then
#Override
protected void onStop() {
super.onStop();
mAdapter.stopListening();
}
Works like it did before the update :-)
I had the same problem. I tried the accepted answer, but the code was failing with null pointer exception in the startListening statement (adapter was null !).
After much research, I added .setLifecycleOwner(this) as in the sample code (see below code for example). It worked like a charm!
FirebaseListOptions<Message> options = new FirebaseListOptions.Builder<Message>()
.setLayout(R.layout.fbmessage_listitem)//Note: The guide doesn't mention this method, without it an exception is thrown that the layout has to be set.
.setQuery(query, Message.class)
.setLifecycleOwner(this) //Added this
.build();
Could it be a typo in the query? notifications instead of notification? Try putting a breakpoint in FirebaseListAdapter#onChildChanged() and see if any events come in.
Most likely, it's because you haven't called FirebaseListAdapter#startListening(). Unless you are using Android Architecture Components with FirebaseListOptions#setLifecycleOwner(...), you must manually manage your adapter's lifecycle. This means calling startListening() in onStart() and stopListening() in onStop().
PS: I would recommend checking out the RecyclerView which has better performance than ListView.
I am developing a sort of monitor which should update a lot of imageviews (fake leds) basing on what i receive from a machine and let the user know the current machine state.
Is there a way to set on the variable a value listener which trigger a method when the value change in which i can update my UI?
Thanks everybody for the help and the comments;
I find out Data Binding was the best option in my case, so after a few attempts i've been able to build a sample project where i've got a an handler which every 1sec increment an integer value by 1.
After that i check if the value is pair or dispair and i set a boolen true or false.
I set up a layout file in which i've got 2 text view and a toggle button. The first text view text is binded to a simple string with my name, the second one is binded to the integer which is incremented each second (a timer) and the Toggle Button is binded to the Boolean.
Here's the Code:
MainActivity
public class MainActivity extends AppCompatActivity {
ActivityMainBinding binding;
android.os.Handler Handler;
User user;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
user = new User("Lorenzo", "Gangemi", 0);
binding.setUser(user);
useHandler();
}
public void useHandler() {
if (Handler != null) {
Handler.postDelayed(RunableHandler, 1000);
Handler.removeCallbacks(RunableHandler);
}
try {
Handler = new Handler();
} catch (Exception e) {
Handler = new Handler(Looper.getMainLooper());
}
Handler.postDelayed(RunableHandler, 1000);
}
private Runnable RunableHandler = new Runnable() {
#Override
public void run() {
user.increment_i();
binding.setUser(user);
useHandler();
}
};
}
User
public class User {
private final String firstName;
private final String lastName;
private boolean pari;
private int i;
public User(String firstName, String lastName, int i) {
this.firstName = firstName;
this.lastName = lastName;
this.i = i;
setPari(checkPari());
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getI() {
return i;
}
public void increment_i()
{
i++;
setPari(checkPari());
}
public boolean checkPari()
{
return i%2==0;
}
public boolean isPari() {
return pari;
}
public void setPari(boolean pari) {
this.pari = pari;
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="mdptech.databinding.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{Integer.toString(user.i)}" />
<ToggleButton
android:text="ToggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/toggleButton"
android:drawableBottom="#drawable/toggle"
android:background="#android:color/transparent"
android:textOff="OFF"
android:textOn="ON"
android:checked="#{user.pari ? true : false}"/>
</LinearLayout>
</layout>
Everything works fine.
As you can see from the code, in the handler Runnable i call the method for increment the timer, but i have to call binding.setUser(user); too for updating the layout.
Is there a way to make this update happen automatically and not becouse i call a method?
That's not a big problem becouse i can create a separate handler which loops every 100ms (for ex.) and in his runnable call binding.setUser(user);, But it will speed up my work a lot.
Thank you.