Related
I've added some code to make my question more clear.
Retrofit interface:
public interface JsonPlaceHolderAPI {
public static final String BASE_URL = "https://jsonplaceholder.typicode.com/";
#GET("todos/{number}")
Call<ResponseBody> getJsonResponse(#Path("number") String number);
}
The repository: --> fetchResponse() takes Viewmodel's MutableLiveData as parameter and uses it to update its value and then trigger View to change its UI.
public class Repository {
private final JsonPlaceHolderAPI api;
public Repository() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.build();
api = retrofit.create(JsonPlaceHolderAPI.class);
}
public void fetchResponse(String number, final MutableLiveData<CharSequence> mld){
final MutableLiveData<CharSequence> ml = new MutableLiveData<>();
api.getJsonResponse(number).enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
mld.setValue(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {}
});
}
}
The viewModel:
public class MainActivityViewModel extends AndroidViewModel {
MutableLiveData<CharSequence> response = new MutableLiveData<>();
Repository repository;
public MainActivityViewModel(#NonNull Application application) {
super(application);
repository = new Repository();
}
public void fetchData(String number) {
response.setValue("Loading data");
repository.fetchResponse(number, response);
}
public LiveData<? extends CharSequence> getLiveData() {
return response;
}
}
The View:
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewModel = ViewModelProviders.of(this).get(MainActivityViewModel.class);
initViews();
viewModel.getLiveData().observe(this, new Observer<CharSequence>() {
#Override
public void onChanged(CharSequence charSequence) {
if (charSequence != null) {
txt.setText(charSequence);
}
}
});
}
...
I am not sure if I should pass the MutableLiveData from the viewModel to the Repository.
Is there any recommended way to let viewModel know that data is ready to be published from Repository??
I have read a lot of questions and articles and still I don't get it. I would love if somebody explain to me a nice way to achieve it!
Api
public interface TodoApi {
#GET("todos/")
Call<List<Todo>> getTodos();
#GET("todos/{id}")
Call<Todo> getTodo(#Path("id") long id);
}
Respository
public class TodoRepository {
private static final String TAG = "TodoRepository";
private static final TodoRepository ourInstance = new TodoRepository();
private TodoApi api;
private MutableLiveData<List<Todo>> todoListLiveData = new MutableLiveData<>();
private MutableLiveData<Todo> todoLiveData = new MutableLiveData<>();
public static TodoRepository getInstance() {
return ourInstance;
}
private TodoRepository() {
api = ApiBuilder.create(TodoApi.class);
}
public LiveData<List<Todo>> getTodos() {
api.getTodos().enqueue(new Callback<List<Todo>>() {
#Override
public void onResponse(#NonNull Call<List<Todo>> call, #NonNull Response<List<Todo>> response) {
todoListLiveData.setValue(response.body());
}
#Override
public void onFailure(#NonNull Call<List<Todo>> call, #NonNull Throwable t) {
Log.d(TAG, "onFailure: failed to fetch todo list from server");
}
});
return todoListLiveData;
}
public LiveData<Todo> getTodo(long id) {
api.getTodo(id).enqueue(new Callback<Todo>() {
#Override
public void onResponse(#NonNull Call<Todo> call, #NonNull Response<Todo> response) {
todoLiveData.setValue(response.body());
}
#Override
public void onFailure(#NonNull Call<Todo> call, #NonNull Throwable t) {
Log.d(TAG, "onFailure: failed to get todo");
}
});
return todoLiveData;
}
}
ViewModel
public class MainActivityViewModel extends ViewModel {
private static final String TAG = "MainActivityViewModel";
private TodoRepository repository = TodoRepository.getInstance();
private MutableLiveData<Boolean> isLoading = new MutableLiveData<>();
private LiveData<List<Todo>> todoLiveData;
public MainActivityViewModel() {
super();
isLoading.setValue(true);
todoLiveData = repository.getTodos();
}
#Override
protected void onCleared() {
super.onCleared();
}
public MutableLiveData<Boolean> getIsLoading() {
return isLoading;
}
public LiveData<List<Todo>> getTodoLiveData() {
return todoLiveData;
}
}
View
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
todoListRecyclerView = findViewById(R.id.todo_recycler_view);
loadingIndicator = findViewById(R.id.todo_loading_indicator);
mViewModel = ViewModelProviders.of(this).get(MainActivityViewModel.class);
getSupportActionBar().setTitle("Todos");
mViewModel.getIsLoading().observe(this, new Observer<Boolean>() {
#Override
public void onChanged(Boolean isLoading) {
if (isLoading) loadingIndicator.setVisibility(View.VISIBLE);
else loadingIndicator.setVisibility(View.GONE);
}
});
mViewModel.getTodoLiveData().observe(this, new Observer<List<Todo>>() {
#Override
public void onChanged(List<Todo> todos) {
mViewModel.getIsLoading().postValue(false);
initRecyclerView(todos);
}
});
}
For full sample
https://github.com/AnvarNazar/RetrofitTypicodeApiExample
I am using quickblox for my android application. I am able to register, login user, even add custom objects and retrieve them from the quick-blox server, but in chatting listing screen my count is coming wrong.
I am getting issue with unread count
Here is my code flow:-
public class ConversationFragment extends Fragment implements Observer, DialogsManager.ManagingDialogsCallbacks {
ConversationViewModel conversationViewModel;
FragmentConversationBinding binding;
QBChatDialogMessageListener allDialogsMessagesListener;
SystemMessagesListener systemMessagesListener;
QBSystemMessagesManager systemMessagesManager;
QBIncomingMessagesManager incomingMessagesManager;
private DialogsManager dialogsManager;
public ConversationFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_conversation, container, false);
getActivity().getWindow().setBackgroundDrawableResource(R.drawable.bg_img);
View view = binding.getRoot();
systemMessagesListener = new SystemMessagesListener();
dialogsManager = new DialogsManager();
return view;
}
private void setUpModel() {
Bundle mBundle = getArguments();
if (mBundle != null) {
conversationViewModel = new ConversationViewModel(getActivity(), binding, getArguments().getString("DialogIdData"));
} else {
conversationViewModel = new ConversationViewModel(getActivity(), binding, "");
}
binding.setConversationViewModel(conversationViewModel);
setUpObserver(conversationViewModel);
}
private void setUpObserver(ConversationViewModel observer) {
observer.addObserver(this);
}
#Override
public void update(Observable o, Object arg) {
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public void onResume() {
super.onResume();
setUpModel();
}
#Override
public void onPause() {
super.onPause();
unregisterQbChatListeners();
}
public void unregisterQbChatListeners() {
if (incomingMessagesManager != null) {
incomingMessagesManager.removeDialogMessageListrener(allDialogsMessagesListener);
}
if (systemMessagesManager != null) {
systemMessagesManager.removeSystemMessageListener(systemMessagesListener);
}
dialogsManager.removeManagingDialogsCallbackListener(this);
}
#Override
public void onDialogCreated(QBChatDialog chatDialog) {
}
#Override
public void onDialogUpdated(String chatDialog) {
}
#Override
public void onNewDialogLoaded(QBChatDialog chatDialog) {
}
private class SystemMessagesListener implements QBSystemMessageListener {
#Override
public void processMessage(final QBChatMessage qbChatMessage) {
dialogsManager.onSystemMessageReceived(qbChatMessage);
}
#Override
public void processError(QBChatException e, QBChatMessage qbChatMessage) {
}
}
private BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
}
};
}
Here is my Fragment viewModel code :-
public class ConversationViewModel extends Observable implements DialogsManager.ManagingDialogsCallbacks {
private static final int REQUEST_DIALOG_ID_FOR_UPDATE = 165;
public Context mContext;
FragmentConversationBinding binding;
private ActionMode currentActionMode;
public QBRequestGetBuilder requestBuilder;
private int skipRecords = 0;
private DialogsAdapter dialogsAdapter;
private QBChatDialogMessageListener allDialogsMessagesListener;
private SystemMessagesListener systemMessagesListener;
private QBSystemMessagesManager systemMessagesManager;
private QBIncomingMessagesManager incomingMessagesManager;
private DialogsManager dialogsManager;
private QBUser currentUser;
private String id;
public ConversationViewModel(Context mContext, FragmentConversationBinding binding, String Dialogid) {
this.mContext = mContext;
this.binding = binding;
this.id = Dialogid;
initUI();
}
public void initUI() {
allDialogsMessagesListener = new AllDialogsMessageListener();
systemMessagesListener = new SystemMessagesListener();
dialogsManager = new DialogsManager();
currentUser = ChatHelper.getCurrentUser();
initList();
registerQbChatListeners();
if (QbDialogHolder.getInstance().getDialogs().size() > 0) {
loadDialogsFromQb(true, true);
} else {
loadDialogsFromQb(false, true);
}
if (!id.isEmpty()) {
loadUpdatedDialog(id);
id = "";
} else {
updateDialogsList();
}
}
public void initList() {
dialogsAdapter = new DialogsAdapter(mContext, new ArrayList<>(QbDialogHolder.getInstance().getDialogs().values()));
binding.listDialogsChats.setAdapter(dialogsAdapter);
binding.listDialogsChats.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
QBChatDialog selectedDialog = (QBChatDialog) parent.getItemAtPosition(position);
if (currentActionMode == null) {
ChatActivity.startForResult(((Activity) mContext), REQUEST_DIALOG_ID_FOR_UPDATE, selectedDialog);
} else {
dialogsAdapter.toggleSelection(selectedDialog);
}
}
});
binding.listDialogsChats.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
QBChatDialog selectedDialog = (QBChatDialog) parent.getItemAtPosition(position);
dialogsAdapter.selectItem(selectedDialog);
return true;
}
});
requestBuilder = new QBRequestGetBuilder();
binding.swipyRefreshLayout.setOnRefreshListener(new SwipyRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh(SwipyRefreshLayoutDirection direction) {
requestBuilder.setSkip(skipRecords += ChatHelper.DIALOG_ITEMS_PER_PAGE);
loadDialogsFromQb(true, false);
}
});
}
private void loadUpdatedDialog(String dialogId) {
ChatHelper.getInstance().getDialogById(dialogId, new QbEntityCallbackImpl<QBChatDialog>() {
#Override
public void onSuccess(QBChatDialog result, Bundle bundle) {
QbDialogHolder.getInstance().addDialog(result);
updateDialogsAdapter();
}
#Override
public void onError(QBResponseException e) {
}
});
}
public void updateDialogsList() {
requestBuilder.setSkip(skipRecords = 0);
loadDialogsFromQb(true, true);
}
#Override
public void onDialogCreated(QBChatDialog chatDialog) {
updateDialogsAdapter();
}
#Override
public void onDialogUpdated(String chatDialog) {
updateDialogsAdapter();
}
#Override
public void onNewDialogLoaded(QBChatDialog chatDialog) {
updateDialogsAdapter();
}
private void registerQbChatListeners() {
incomingMessagesManager = QBChatService.getInstance().getIncomingMessagesManager();
systemMessagesManager = QBChatService.getInstance().getSystemMessagesManager();
if (incomingMessagesManager != null) {
incomingMessagesManager.addDialogMessageListener(allDialogsMessagesListener != null
? allDialogsMessagesListener : new AllDialogsMessageListener());
}
if (systemMessagesManager != null) {
systemMessagesManager.addSystemMessageListener(systemMessagesListener != null
? systemMessagesListener : new SystemMessagesListener());
}
dialogsManager.addManagingDialogsCallbackListener(this);
}
private class SystemMessagesListener implements QBSystemMessageListener {
#Override
public void processMessage(final QBChatMessage qbChatMessage) {
dialogsManager.onSystemMessageReceived(qbChatMessage);
}
#Override
public void processError(QBChatException e, QBChatMessage qbChatMessage) {
}
}
private class AllDialogsMessageListener extends QbChatDialogMessageListenerImp {
#Override
public void processMessage(final String dialogId, final QBChatMessage qbChatMessage, Integer senderId) {
if (!senderId.equals(ChatHelper.getCurrentUser().getId())) {
dialogsManager.onGlobalMessageReceived(dialogId, qbChatMessage);
}
}
}
public void updateDialogsAdapter() {
dialogsAdapter.updateList(new ArrayList<>(QbDialogHolder.getInstance().getDialogs().values()));
}
public void loadDialogsFromQb(final boolean silentUpdate, final boolean clearDialogHolder) {
if (!silentUpdate) {
binding.progressDialogs.setVisibility(View.VISIBLE);
}
ChatHelper.getInstance().getDialogs(requestBuilder, new QBEntityCallback<ArrayList<QBChatDialog>>() {
#Override
public void onSuccess(ArrayList<QBChatDialog> dialogs, Bundle bundle) {
binding.progressDialogs.setVisibility(View.GONE);
binding.swipyRefreshLayout.setRefreshing(false);
if (clearDialogHolder) {
QbDialogHolder.getInstance().clear();
}
QbDialogHolder.getInstance().addDialogs(dialogs);
updateDialogsAdapter();
}
#Override
public void onError(QBResponseException e) {
binding.progressDialogs.setVisibility(View.GONE);
binding.swipyRefreshLayout.setRefreshing(false);
Toast.makeText(mContext, e.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
}
Here is my Chat Activity:-
public class ChatActivity extends BindingActivity<ActivityChatBinding> implements OnImagePickedListener {
ChatViewModel chatViewModel;
public static final int REQUEST_CODE_ATTACHMENT = 721;
public static final String EXTRA_DIALOG_ID = "dialogId";
#Override
protected int getLayoutId() {
return R.layout.activity_chat;
}
public static void startForResult(Activity activity, int code, QBChatDialog dialogId) {
Intent intent = new Intent(activity, ChatActivity.class);
intent.putExtra(ChatActivity.EXTRA_DIALOG_ID, dialogId);
activity.startActivityForResult(intent, code);
}
#Override
public void setInitBinding() {
getWindow().setBackgroundDrawableResource(R.drawable.bg_img);
chatViewModel = new ChatViewModel(this, binding);
binding.setChatViewModel(chatViewModel);
}
#Override
protected void setUpObserver() {
chatViewModel.addObserver(this);
}
#Override
public void update(Observable o, Object arg) {
}
#Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
#Override
public void onBackPressed() {
binding.getChatViewModel().releaseChat();
binding.getChatViewModel().sendDialogId();
super.onBackPressed();
}
#Override
public void onImagePicked(int requestCode, File file) {
switch (requestCode) {
case REQUEST_CODE_ATTACHMENT:
binding.getChatViewModel().attachmentPreviewAdapter.add(file);
break;
}
}
#Override
public void onImagePickError(int requestCode, Exception e) {
Toast.makeText(this, e.toString(), Toast.LENGTH_SHORT).show();
}
#Override
public void onImagePickClosed(int requestCode) {
}
}
Chat Activity View Model :-
public class ChatViewModel extends Observable {
public Context mContext;
ActivityChatBinding binding;
public static final String TAG = ChatActivity.class.getSimpleName();
public static final int REQUEST_CODE_ATTACHMENT = 721;
public static final String PROPERTY_SAVE_TO_HISTORY = "save_to_history";
public static final String EXTRA_DIALOG_ID = "dialogId";
public ChatAdapter chatAdapter;
public AttachmentPreviewAdapter attachmentPreviewAdapter;
public QBChatDialog qbChatDialog;
public ArrayList<QBChatMessage> unShownMessages;
public int skipPagination = 0;
public ChatMessageListener chatMessageListener;
QBPrivacyList qbPrivacyList;
public static boolean mBlock;
public static int userId, recipientId;
public QBPrivacyListsManager privacyListsManager;
public ObservableField<String> chatMessage = new ObservableField<>("");
public ChatViewModel(Context mContext, ActivityChatBinding binding) {
this.mContext = mContext;
this.binding = binding;
Log.v(TAG, "onCreate ChaActivity on Thread ID = " + Thread.currentThread().getId());
qbChatDialog = (QBChatDialog) ((Activity) mContext).getIntent().getSerializableExtra(EXTRA_DIALOG_ID);
Log.v(TAG, "deserialized dialog = " + qbChatDialog);
qbChatDialog.initForChat(QBChatService.getInstance());
chatMessageListener = new ChatMessageListener();
qbPrivacyList = new QBPrivacyList();
privacyListsManager = QBChatService.getInstance().getPrivacyListsManager();
qbChatDialog.addMessageListener(chatMessageListener);
initViews();
initChat();
}
public void initViews() {
try {
recipientId = qbChatDialog.getRecipientId();
userId = qbChatDialog.getUserId();
getPrivacyList();
} catch (SmackException.NotConnectedException e) {
e.printStackTrace();
} catch (XMPPException.XMPPErrorException e) {
e.printStackTrace();
} catch (SmackException.NoResponseException e) {
e.printStackTrace();
}
attachmentPreviewAdapter = new AttachmentPreviewAdapter(mContext,
new AttachmentPreviewAdapter.OnAttachmentCountChangedListener() {
#Override
public void onAttachmentCountChanged(int count) {
binding.attachmentContainer.setVisibility(count == 0 ? View.GONE : View.VISIBLE);
}
},
new AttachmentPreviewAdapter.OnAttachmentUploadErrorListener() {
#Override
public void onAttachmentUploadError(QBResponseException e) {
Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show();
}
});
binding.attachmentAdapter.setAdapter(attachmentPreviewAdapter);
}
public void onAttachmentsClick(View view) {
new ImagePickHelper().pickAnImage(((FragmentActivity) mContext), REQUEST_CODE_ATTACHMENT);
}
private void initChat() {
switch (qbChatDialog.getType()) {
case PRIVATE:
loadDialogUsers();
break;
default:
Toaster.shortToast(String.format("%s %s", getString(R.string.chat_unsupported_type), qbChatDialog.getType().name()));
((Activity) mContext).finish();
break;
}
}
public void loadDialogUsers() {
ChatHelper.getInstance().getUsersFromDialog(qbChatDialog, new QBEntityCallback<ArrayList<QBUser>>() {
#Override
public void onSuccess(ArrayList<QBUser> users, Bundle bundle) {
String chatName = QbDialogUtils.getDialogName(qbChatDialog);
binding.dialogName.setText(chatName);
loadChatHistory();
}
#Override
public void onError(QBResponseException e) {
Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show();
}
});
}
public void loadChatHistory() {
ChatHelper.getInstance().loadChatHistory(qbChatDialog, skipPagination, new QBEntityCallback<ArrayList<QBChatMessage>>() {
#Override
public void onSuccess(ArrayList<QBChatMessage> messages, Bundle args) {
Collections.reverse(messages);
if (chatAdapter == null) {
chatAdapter = new ChatAdapter(mContext, qbChatDialog, messages);
chatAdapter.setPaginationHistoryListener(new PaginationHistoryListener() {
#Override
public void downloadMore() {
loadChatHistory();
}
});
chatAdapter.setOnItemInfoExpandedListener(new ChatAdapter.OnItemInfoExpandedListener() {
#Override
public void onItemInfoExpanded(final int position) {
if (isLastItem(position)) {
((Activity) mContext).runOnUiThread(new Runnable() {
#Override
public void run() {
binding.listChat.setSelection(position);
}
});
} else {
binding.listChat.smoothScrollToPosition(position);
}
}
private boolean isLastItem(int position) {
return position == chatAdapter.getCount() - 1;
}
});
if (unShownMessages != null && !unShownMessages.isEmpty()) {
List<QBChatMessage> chatList = chatAdapter.getList();
for (QBChatMessage message : unShownMessages) {
if (!chatList.contains(message)) {
chatAdapter.add(message);
}
}
}
binding.listChat.setAdapter(chatAdapter);
binding.listChat.setAreHeadersSticky(false);
binding.listChat.setDivider(null);
} else {
chatAdapter.addList(messages);
binding.listChat.setSelection(messages.size());
}
binding.progressBar.setVisibility(View.GONE);
}
#Override
public void onError(QBResponseException e) {
binding.progressBar.setVisibility(View.GONE);
skipPagination -= ChatHelper.CHAT_HISTORY_ITEMS_PER_PAGE;
Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show();
}
});
skipPagination += ChatHelper.CHAT_HISTORY_ITEMS_PER_PAGE;
QBRestChatService.markMessagesAsRead(qbChatDialog.getDialogId(), null);
}
public class ChatMessageListener extends QbChatDialogMessageListenerImp {
#Override
public void processMessage(String s, QBChatMessage qbChatMessage, Integer integer) {
showMessage(qbChatMessage);
}
}
public void showMessage(QBChatMessage message) {
if (chatAdapter != null) {
chatAdapter.add(message);
scrollMessageListDown();
} else {
if (unShownMessages == null) {
unShownMessages = new ArrayList<>();
}
unShownMessages.add(message);
}
}
public void scrollMessageListDown() {
binding.listChat.setSelection(binding.listChat.getCount() - 1);
}
public void onSendChatClick(View view) {
int totalAttachmentsCount = attachmentPreviewAdapter.getCount();
Collection<QBAttachment> uploadedAttachments = attachmentPreviewAdapter.getUploadedAttachments();
if (!uploadedAttachments.isEmpty()) {
if (uploadedAttachments.size() == totalAttachmentsCount) {
for (QBAttachment attachment : uploadedAttachments) {
sendChatMessage(null, attachment);
}
} else {
Toaster.shortToast(R.string.chat_wait_for_attachments_to_upload);
}
}
String text = binding.getChatViewModel().chatMessage.get().trim();
if (!TextUtils.isEmpty(text)) {
sendChatMessage(text, null);
}
}
public void sendChatMessage(String text, QBAttachment attachment) {
QBChatMessage chatMessage = new QBChatMessage();
if (attachment != null) {
chatMessage.addAttachment(attachment);
} else {
chatMessage.setBody(text);
}
chatMessage.setProperty(PROPERTY_SAVE_TO_HISTORY, "1");
chatMessage.setDateSent(System.currentTimeMillis() / 1000);
chatMessage.setMarkable(true);
if (!QBDialogType.PRIVATE.equals(qbChatDialog.getType()) && !qbChatDialog.isJoined()) {
Toaster.shortToast("You're still joining a group chat, please wait a bit");
return;
}
try {
qbChatDialog.sendMessage(chatMessage);
if (QBDialogType.PRIVATE.equals(qbChatDialog.getType())) {
showMessage(chatMessage);
}
if (attachment != null) {
attachmentPreviewAdapter.remove(attachment);
} else {
binding.getChatViewModel().chatMessage.set("");
}
} catch (SmackException.NotConnectedException e) {
Log.w(TAG, e);
Toaster.shortToast("Can't send a message, You are not connected to chat");
}
}
public void leaveGroupDialog() {
try {
ChatHelper.getInstance().leaveChatDialog(qbChatDialog);
} catch (XMPPException | SmackException.NotConnectedException e) {
Log.w(TAG, e);
}
}
public void releaseChat() {
qbChatDialog.removeMessageListrener(chatMessageListener);
if (!QBDialogType.PRIVATE.equals(qbChatDialog.getType())) {
leaveGroupDialog();
}
}
public void sendDialogId() {
Intent result = new Intent();
result.putExtra(EXTRA_DIALOG_ID, qbChatDialog.getDialogId());
((Activity) mContext).setResult(RESULT_OK, result);
}
public void finishActivity(View view) {
((Activity) mContext).finish();
}
public void clickButtonBlock(View view) {
AppUtils.dialog(mContext);
if (mBlock) {
onClickUnblock();
} else {
onClickBlock();
}
}
public void getPrivacyList() throws SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NoResponseException {
QBPrivacyListsManager privacyListsManager = QBChatService.getInstance().getPrivacyListsManager();
QBPrivacyList privacyList = privacyListsManager.getPrivacyList("public");
List<QBPrivacyListItem> items = privacyList.getItems();
int i;
for (i = 0; i < privacyList.getItems().size(); i++) {
QBPrivacyListItem item = items.get(i);
String valueForType = item.getValueForType();
String[] splitvalueType = valueForType.split("-");
String blockId = splitvalueType[0];
if (blockId.equalsIgnoreCase(String.valueOf(recipientId))) {
mBlock = true;
binding.tvBlock.setText("Unblock");
break;
} else {
binding.tvBlock.setText("Block");
}
}
}
}
The problem is chatDialog.getUnreadMessageCount() giving me count 2 when I once enter in chat room and come back to my chat listing page.
Description on issue:-
Example:-
I have installed my application in two devices. when i send message from one (Device A) to other (Device B).The device B will display correct Unread count i.e 1 . Now when i click on chat dialog and get entered inside the chat room (of Device B). and come back to it's listing page and then again try to send message from Device A to device B . This time the unread count comes as 2 but it should be one as i already viewed my previous message. This get more worse if i try to open my chat room again to read the message(Device B) and get back to my listing page(Device B) after reading the message . This time if I send the message from Device A to Device B, Then the count came out as "5" just for my one message this thing is making me sick, I debugged the whole code, it's chatDialog .getUnreadMessageCount() who is returning me count "5", I don't know why my previous messages are not counted as being read messages and why everytime I open chat room one additional number gets added up inside the unread count.Please help me out , i am scratching my head from past two days.
Your help will be greatly Appreciated.
Thanks
Problem is that your AllDialogsMessageListener is not getting unregistered so unregister it by calling unregisterQbChatListeners(); when you will be opening your chat room screen . Every thing will work fine once you do that.
This what i mean :-
binding.listDialogsChats.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
QBChatDialog selectedDialog = (QBChatDialog) parent.getItemAtPosition(position);
if (currentActionMode == null) {
unregisterQbChatListeners();
ChatActivity.startForResult(((Activity) mContext), REQUEST_DIALOG_ID_FOR_UPDATE, selectedDialog);
} else {
dialogsAdapter.toggleSelection(selectedDialog);
}
}
});
private void unregisterQbChatListeners() {
if (incomingMessagesManager != null) {
incomingMessagesManager.removeDialogMessageListrener(allDialogsMessagesListener);
}
if (systemMessagesManager != null) {
systemMessagesManager.removeSystemMessageListener(systemMessagesListener);
}
dialogsManager.removeManagingDialogsCallbackListener(this);
}
cheers!!!
Please use this code below :
QBChatService.markMessagesAsRead("YOUR_DIALOG_ID", null, new QBEntityCallback<Void>() {
#Override
public void onSuccess(Void aVoid, Bundle bundle) {
QBRequestGetBuilder requestBuilder = new QBRequestGetBuilder();
requestBuilder.eq("_id", Team.getCurrent().getChatId());
QBChatService.getChatDialogs(null, requestBuilder, new QBEntityCallback<ArrayList<QBDialog>>() {
#Override
public void onSuccess(ArrayList<QBDialog> qbDialogs, Bundle bundle) {
if (qbDialogs != null && qbDialogs.size() > 0) {
QBDialog dialog = qbDialogs.get(0);//here you get your dialog with unreadMessageCount = 0
}
}
#Override
public void onError(QBResponseException e) {
}
});
}
#Override
public void onError(QBResponseException e) {
}
});
i want to query results from custom adapter by comparing query text with list in RecycleView, it successfully gets results from query then i make list of results and update the existing adapter but it does not effect anything, i use below code fot that
combinationMessagesList = createCombinationMessagesList();
combinationMessages = createCombinationMessagesList();
combinationMessages.clear();
for (int i = 0; i < combinationMessagesList.size(); i++) {
CombinationMessage message = combinationMessagesList.get(i);
if (message.getBody().toString().equals(search.getText().toString())){
combinationMessages.add(combinationMessagesList.get(i));
}
}
messagesAdapter.setList(combinationmessagesList);
messagesAdapter.notifyDataSetChanged();
}
PrivateDialogActivity
public class PrivateDialogActivity extends BaseDialogActivity {
private FriendOperationAction friendOperationAction;
private FriendObserver friendObserver;
private int operationItemPosition;
private final String TAG = "PrivateDialogActivity";
EditText search;
public static void start(Context context, User opponent, Dialog dialog) {
Intent intent = new Intent(context, PrivateDialogActivity.class);
intent.putExtra(QBServiceConsts.EXTRA_OPPONENT, opponent);
intent.putExtra(QBServiceConsts.EXTRA_DIALOG, dialog);
context.startActivity(intent);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initFields();
context = this;
if (dialog == null) {
finish();
}
setUpActionBarWithUpButton();
if (isNetworkAvailable()) {
deleteTempMessages();
}
addObservers();
initMessagesRecyclerView();
search = (EditText) findViewById(R.id.search_edittext);
Button button = (Button) findViewById(R.id.search);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
showResult();
}
});
}
private void showResult() {
combinationMessagesList = createCombinationMessagesList();
combinationMessages = createCombinationMessagesList();
combinationMessages.clear();
for (int i = 0; i < combinationMessagesList.size(); i++) {
CombinationMessage message = combinationMessagesList.get(i);
if (message.getBody().toString().equals(search.getText().toString())){
combinationMessages.add(combinationMessagesList.get(i));
}
}
messagesAdapter.setList(combinationMessagesList);
messagesAdapter.notifyDataSetChanged();
}
#Override
protected void addActions() {
super.addActions();
addAction(QBServiceConsts.ACCEPT_FRIEND_SUCCESS_ACTION, new AcceptFriendSuccessAction());
addAction(QBServiceConsts.ACCEPT_FRIEND_FAIL_ACTION, failAction);
addAction(QBServiceConsts.REJECT_FRIEND_SUCCESS_ACTION, new RejectFriendSuccessAction());
addAction(QBServiceConsts.REJECT_FRIEND_FAIL_ACTION, failAction);
updateBroadcastActionList();
}
#Override
protected void onResume() {
super.onResume();
checkForCorrectChat();
if (isNetworkAvailable()) {
startLoadDialogMessages();
}
checkMessageSendingPossibility();
}
#Override
protected void onDestroy() {
super.onDestroy();
deleteObservers();
}
#Override
protected void updateActionBar() {
setOnlineStatus(opponentUser);
checkActionBarLogo(opponentUser.getAvatar(), R.drawable.placeholder_user);
}
#Override
protected void onConnectServiceLocally(QBService service) {
onConnectServiceLocally();
setOnlineStatus(opponentUser);
}
#Override
protected void onFileLoaded(QBFile file, String dialogId) {
if(!dialogId.equals(dialog.getDialogId())){
return;
}
try {
privateChatHelper.sendPrivateMessageWithAttachImage(file, opponentUser.getUserId(), null, null);
} catch (QBResponseException exc) {
ErrorUtils.showError(this, exc);
}
}
#Override
protected Bundle generateBundleToInitDialog() {
Bundle bundle = new Bundle();
bundle.putInt(QBServiceConsts.EXTRA_OPPONENT_ID, opponentUser.getUserId());
return bundle;
}
#Override
protected void initMessagesRecyclerView() {
super.initMessagesRecyclerView();
messagesAdapter = new PrivateDialogMessagesAdapter(this, friendOperationAction, combinationMessagesList, this, dialog);
messagesRecyclerView.addItemDecoration(
new StickyRecyclerHeadersDecoration((StickyRecyclerHeadersAdapter) messagesAdapter));
findLastFriendsRequest();
messagesRecyclerView.setAdapter(messagesAdapter);
scrollMessagesToBottom();
}
#Override
protected void updateMessagesList() {
initActualExtras();
checkForCorrectChat();
int oldMessagesCount = messagesAdapter.getAllItems().size();
this.combinationMessagesList = createCombinationMessagesList();
Log.d(TAG, "combinationMessagesList = " + combinationMessagesList);
messagesAdapter.setList(combinationMessagesList);
findLastFriendsRequest();
checkForScrolling(oldMessagesCount);
}
private void initActualExtras() {
opponentUser = (User) getIntent().getExtras().getSerializable(QBServiceConsts.EXTRA_OPPONENT);
dialog = (Dialog) getIntent().getExtras().getSerializable(QBServiceConsts.EXTRA_DIALOG);
}
#Override
public void notifyChangedUserStatus(int userId, boolean online) {
super.notifyChangedUserStatus(userId, online);
if (opponentUser != null && opponentUser.getUserId() == userId) {
setOnlineStatus(opponentUser);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.private_dialog_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
boolean isFriend = DataManager.getInstance().getFriendDataManager().getByUserId(
opponentUser.getUserId()) != null;
if (!isFriend && item.getItemId() != android.R.id.home) {
ToastUtils.longToast(R.string.dialog_user_is_not_friend);
return true;
}
switch (item.getItemId()) {
case R.id.action_audio_call:
callToUser(opponentUser, QBRTCTypes.QBConferenceType.QB_CONFERENCE_TYPE_AUDIO);
break;
case R.id.switch_camera_toggle:
callToUser(opponentUser, QBRTCTypes.QBConferenceType.QB_CONFERENCE_TYPE_VIDEO);
break;
default:
super.onOptionsItemSelected(item);
}
return true;
}
#Override
protected void checkMessageSendingPossibility() {
boolean enable = dataManager.getFriendDataManager().existsByUserId(opponentUser.getUserId()) && isNetworkAvailable();
checkMessageSendingPossibility(enable);
}
#OnClick(R.id.toolbar)
void openProfile(View view) {
UserProfileActivity.start(this, opponentUser.getUserId());
}
private void initFields() {
chatHelperIdentifier = QBService.PRIVATE_CHAT_HELPER;
friendOperationAction = new FriendOperationAction();
friendObserver = new FriendObserver();
initActualExtras();
// opponentUser = (User) getIntent().getExtras().getSerializable(QBServiceConsts.EXTRA_OPPONENT);
// dialog = (Dialog) getIntent().getExtras().getSerializable(QBServiceConsts.EXTRA_DIALOG);
combinationMessagesList = createCombinationMessagesList();
title = opponentUser.getFullName();
}
private void addObservers() {
dataManager.getFriendDataManager().addObserver(friendObserver);
}
private void deleteObservers() {
dataManager.getFriendDataManager().deleteObserver(friendObserver);
}
private void findLastFriendsRequest() {
((PrivateDialogMessagesAdapter) messagesAdapter).findLastFriendsRequestMessagesPosition();
messagesAdapter.notifyDataSetChanged();
}
private void setOnlineStatus(User user) {
if (user != null) {
if (friendListHelper != null) {
String offlineStatus = getString(R.string.last_seen, DateUtils.toTodayYesterdayShortDateWithoutYear2(user.getLastLogin()),
DateUtils.formatDateSimpleTime(user.getLastLogin()));
setActionBarSubtitle(
OnlineStatusUtils.getOnlineStatus(this, friendListHelper.isUserOnline(user.getUserId()), offlineStatus));
}
}
}
public void sendMessage(View view) {
sendMessage(true);
}
private void callToUser(User user, QBRTCTypes.QBConferenceType qbConferenceType) {
if (!isChatInitializedAndUserLoggedIn()) {
ToastUtils.longToast(R.string.call_chat_service_is_initializing);
return;
}
List<QBUser> qbUserList = new ArrayList<>(1);
qbUserList.add(UserFriendUtils.createQbUser(user));
CallActivity.start(PrivateDialogActivity.this, qbUserList, qbConferenceType, null);
}
private void acceptUser(final int userId) {
if (isNetworkAvailable()) {
if (!isChatInitializedAndUserLoggedIn()) {
ToastUtils.longToast(R.string.call_chat_service_is_initializing);
return;
}
showProgress();
QBAcceptFriendCommand.start(this, userId);
} else {
ToastUtils.longToast(R.string.dlg_fail_connection);
return;
}
}
private void rejectUser(final int userId) {
if (isNetworkAvailable()) {
if (!isChatInitializedAndUserLoggedIn()) {
ToastUtils.longToast(R.string.call_chat_service_is_initializing);
return;
}
showRejectUserDialog(userId);
} else {
ToastUtils.longToast(R.string.dlg_fail_connection);
return;
}
}
private void showRejectUserDialog(final int userId) {
User user = DataManager.getInstance().getUserDataManager().get(userId);
if (user == null) {
return;
}
TwoButtonsDialogFragment.show(getSupportFragmentManager(),
getString(R.string.dialog_message_reject_friend, user.getFullName()),
new MaterialDialog.ButtonCallback() {
#Override
public void onPositive(MaterialDialog dialog) {
super.onPositive(dialog);
showProgress();
QBRejectFriendCommand.start(PrivateDialogActivity.this, userId);
}
});
}
private void checkForCorrectChat() {
Dialog updatedDialog = null;
if (dialog != null) {
updatedDialog = dataManager.getDialogDataManager().getByDialogId(dialog.getDialogId());
} else {
finish();
}
if (updatedDialog == null) {
finish();
} else {
dialog = updatedDialog;
}
}
private class FriendOperationAction implements FriendOperationListener {
#Override
public void onAcceptUserClicked(int position, int userId) {
operationItemPosition = position;
acceptUser(userId);
}
#Override
public void onRejectUserClicked(int position, int userId) {
operationItemPosition = position;
rejectUser(userId);
}
}
private class AcceptFriendSuccessAction implements Command {
#Override
public void execute(Bundle bundle) {
((PrivateDialogMessagesAdapter) messagesAdapter).clearLastRequestMessagePosition();
messagesAdapter.notifyItemChanged(operationItemPosition);
startLoadDialogMessages();
hideProgress();
}
}
private class RejectFriendSuccessAction implements Command {
#Override
public void execute(Bundle bundle) {
((PrivateDialogMessagesAdapter) messagesAdapter).clearLastRequestMessagePosition();
messagesAdapter.notifyItemChanged(operationItemPosition);
startLoadDialogMessages();
hideProgress();
}
}
private class FriendObserver implements Observer {
#Override
public void update(Observable observable, Object data) {
if (data != null && data.equals(FriendDataManager.OBSERVE_KEY)) {
checkForCorrectChat();
checkMessageSendingPossibility();
}
}
}
}
it was my own mistake it code, i made the code work by changing messagesAdapter.setList(combinationmessagesList)
to
messagesAdapter.setList(combinationMessages);
I'm buliding a simple android app, and i got this error when i try to send data to API. I use RxJava and Retrofit, and I use Model View Presenter.
I got this error "btnservice is a null object reference"
I always got btnservice null, please help to solve this.
Thank you
This the JSON
request:
{
name: '',
mobile: '',
email: ''
}
This my BaseActivtiy
public abstract class BaseActivity extends AppCompatActivity {
#BindString(R.string.loading)
public String loading;
#BindInt(R.integer.success_code)
public int successCode;
#BindInt(R.integer.success_activication_code)
public int activicationSuccessCode;
protected BTNService btnService;
protected abstract int getLayout();
private ProgressDialog progressDialog;
private CompositeSubscription subscriptions;
protected RxBus bus;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayout());
Icepick.restoreInstanceState(this, savedInstanceState);
ButterKnife.bind(this);
this.progressDialog = new ProgressDialog(this);
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
#Override
protected void onStart() {
super.onStart();
this.subscriptions = new CompositeSubscription();
}
#Override
public void onStop() {
super.onStop();
}
#Override
protected void onDestroy() {
super.onDestroy();
this.progressDialog.dismiss();
}
public BTNService getBTNService() {
return btnService;
}
public RxBus getBus() {
return bus;
}
public void showProgressDialog(String message) {
if (progressDialog != null) {
progressDialog.setMessage(message);
progressDialog.show();
}
}
public void dismissProgressDialog() {
progressDialog.hide();
}
public boolean isFragmentNotNull(String tag) {
if (getSupportFragmentManager().findFragmentByTag(tag) != null) {
return true;
} else {
return false;
}
}
public boolean isFragmentVisible(String tag) {
if (isFragmentNotNull(tag)
&& getSupportFragmentManager().findFragmentByTag(tag).isVisible()) {
return true;
} else {
return false;
}
}
}
This my BaseFragment
public abstract class BaseFragment extends Fragment implements Validator.ValidationListener {
#BindString(R.string.loading)
public String loading;
#BindInt(R.integer.success_code)
public int successCode;
#BindInt(R.integer.success_activication_code)
public int activicationSuccessCode;
#BindString(R.string.connection_error)
public String connectionError;
protected abstract int getLayout();
protected Validator validator;
private CompositeSubscription subscriptions;
protected RxBus bus;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
this.bus = ((BaseActivity) getActivity()).getBus();
}
#Override
public void onStart() {
super.onStart();
this.subscriptions = new CompositeSubscription();
/* this.subscriptions
.add(bus.toObserverable()
.subscribe(new Action1<Object>() {
#Override
public void call(Object event) {
if (event instanceof RxBusObject) {
RxBusObject busObject = (RxBusObject) event;
busHandler();
}
}
})
);*/
}
#Override
public void onStop() {
super.onStop();
this.subscriptions.clear();
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(getLayout(), container, false);
ButterKnife.bind(this, view);
return view;
}
public RxBus getBus() {
return this.bus;
}
#Override
public void onValidationSucceeded() {
}
#Override
public void onValidationFailed(List<ValidationError> errors) {
for (ValidationError error : errors) {
View view = error.getView();
String message = error.getCollatedErrorMessage(getActivity());
if (view instanceof EditText) {
EditText et = ((EditText) view);
et.setError(message);
et.requestFocus();
} else {
Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
}
}
}
public void showSuccessDialog(String message, final Boolean isFinishActivity) {
new MaterialDialog.Builder(getActivity())
.iconRes(R.mipmap.ic_launcher)
.title(getString(R.string.success).toUpperCase())
.titleColor(Color.WHITE)
.content(message)
.contentColor(Color.WHITE)
.positiveText(R.string.ok)
.positiveColor(Color.WHITE)
.onPositive(new MaterialDialog.SingleButtonCallback() {
#Override
public void onClick(MaterialDialog dialog, DialogAction which) {
if (isFinishActivity) {
getActivity().finish();
}
}
})
.cancelable(false)
.show();
}
public void showProgressDialog(String message) {
((BaseActivity) getActivity()).showProgressDialog(message);
}
public Validator getValidator() {
return validator;
}
public BTNService.Api getApi() {
return ((BaseActivity) getActivity()).getBTNService().getApi();
}
public void dismissProgressDialog() {
((BaseActivity) getActivity()).dismissProgressDialog();
}
}
This my Retrofit Class
public BTNService(Context context) {
if (retrofit==null) {
Retrofit retrofit = new Retrofit.Builder()
.client(provideOkHttpClient(context))
.baseUrl(BASE_URL)
.build();
this.api = retrofit.create(Api.class);
}
}
private OkHttpClient provideOkHttpClient(final Context context) {
HttpLoggingInterceptor httpLoggingInterceptorinterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptorinterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(httpLoggingInterceptorinterceptor);
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
return response;
}
});
return httpClient.build();
}
public Api getApi() {
return api;
}
public interface Api {
#POST(PORTAL_URL + "customer/register")
Observable<SignUpResponse> regsiterCustomer(#Path("email") String Email,
#Path("name") String Name,
#Path("mobile") String PhoneNumber);
}
and this my Presenter
public class SignUpPresenter {
private SignUpFragment fragment;
public SignUpPresenter(SignUpFragment fragment) {
this.fragment = fragment;
}
public SignUpRequest constructSignUpRequest() {
SignUpRequest request = new SignUpRequest();
request.setName(getAndTrimValueFromEditText(fragment.etName));
request.setEmail(getAndTrimValueFromEditText(fragment.etEmail));
request.setMobile(getAndTrimValueFromEditText(fragment.etPhone));
return request;
}
private String getAndTrimValueFromEditText(EditText e) {
return e.getText().toString().trim();
}
void signup (){
this.register(constructSignUpRequest());
}
void register(final SignUpRequest signUpRequest) {
fragment.showProgressDialog(fragment.loading);
fragment.getApi().regsiterCustomer(
signUpRequest.getName(),
signUpRequest.getEmail(),
signUpRequest.getMobile())
.subscribeOn(Schedulers.io())
.subscribe(new Observer<GenericResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
fragment.dismissProgressDialog();
Timber.e(e.getLocalizedMessage());
Toast.makeText(fragment.getContext(), fragment.connectionError, Toast.LENGTH_SHORT).show();
}
#Override
public void onNext(GenericResponse genericResponse) {
fragment.dismissProgressDialog();
Toast.makeText(fragment.getContext(), genericResponse.getInfo(), Toast.LENGTH_SHORT).show();
if (genericResponse.getCode() == fragment.successCode) {
/*fragment.gotoActivationCodeActivity(SignUpRequest.getEmail(), SignUpRequest.get());*/
fragment.gotoQuestionActivity(signUpRequest.getEmail(), signUpRequest.getEmail(), signUpRequest.getMobile());
}
}
});
}
public static <T> Function<BaseResponse<T>, Observable<T>> convertDataFlatMap() {
return new Function<BaseResponse<T>, Observable<T>>() {
#Override
public Observable<T> apply(BaseResponse<T> response) {
if (!Constants.SUCCESS_CODE.equals(response.getStatus_code())) {
BaseErrorResponse baseErrorResponse = new BaseErrorResponse();
baseErrorResponse.setError(response.getStatus_code(),
response.getStatus());
return Observable.error(BaseException.toServerError(baseErrorResponse));
}
T data = response.getData();
if (null == data) {
return Observable.empty();
}
return Observable.just(response.getData());
}
};
}
I'm using Rxjava 2, Maybe useful for you.
I am a complete beginner on rx-java and rx-android. I've heard the learning curve is quite steep in the beginning.
Im trying to replace all Eventbus based code to a more typesafe alternative by using rx-android.
I've set up this snippet to create observables from edit text text change events:
MainActivity
RxUtils.createEditTextChangeObservable(txtInput).throttleLast(200, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()).subscribe(new Action1<EditText>() {
#Override
public void call(EditText editText) {
searchStopResultFragment.query(editText.getText().toString());
}
});
RxUtils:
public static Observable<EditText> createEditTextChangeObservable(final EditText editText){
return Observable.create(new Observable.OnSubscribe<EditText>() {
#Override
public void call(final Subscriber<? super EditText> subscriber) {
editText.addTextChangedListener(new 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) {
if (subscriber.isUnsubscribed()) return;
subscriber.onNext(editText);
}
});
}
});
}
SearchStopResultFragment:
public void query(String query){
lastQuery = query;
resultObservable = StopProvider.getStopResultObservable(getActivity().getContentResolver(),query);
subscription = resultObservable.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<List<Stop>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Stop> stops) {
if(!lastQuery.equals("")) {
if(stops.size()>0) {
ArrayList<AdapterItem> items = adapter.getItems();
items.clear();
for (Stop stop : stops) {
SearchResultStopItem item = new SearchResultStopItem(stop, SearchResultStopItem.STOP);
items.add(item);
}
adapter.setItems(items);
adapter.notifyDataSetChanged();
}else{
//DO A NOTHER ASYNC QUERY TO FETCH RESULTS
}
}else{
showStartItems();
}
}
});
}
It feels like i'm doing this wrong. I create new observables from the query method in my fragment on every text change event. I also want to create a new async lookup operation based off the result in StopProvider.getStopResultObservable (see the comment)
Any thoughs?
Here is what I came up with:
RxUtils.createEditTextChangeObservable(txtInput)
.throttleLast(200, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.map(EXTRACT_STRING)
.filter(STRING_IS_NOT_EMPTY)
.concatMap(new Func1<EditText, Observable<Pair<String,List<Stop>>>>() {
#Override
public Observable<Pair<String, List<Stop>>> call(final String query) {
return StopProvider.getStopResultObservable(getContentResolver(), query)
.map(new Func1<List<Stop>, Pair<String, List<Stop>>>() {
// I think this map is a bit more readable than the
// combineLatest, and since "query" should not be changing
// anyway, the result should be the same (you have to
// declare it as final in the method signature, though
#Override
public Pair<String, List<Stop>> call(List<Stop> stops) {
return new Pair(query, stops);
}
});
}
)
.concatMap(new Func1<Pair<String, List<Stop>>, Observable<List<Stop>>>() {
#Override
public Observable<List<Stop>> call(Pair<String, List<Stop>> queryAndStops) {
if (queryAndStops.second.size() == 0) {
return RestClient.service().locationName(queryAndStops.first)
.map(new Func1<LocationNameResponse, List<Stop>>() {
#Override
public List<Stop> call(LocationNameResponse locationNameResponse) {
// since there was no if-else in your original code (you were always
// just wrapping the List in an Observable) I removed that, too
return locationNameResponse.getAddresses();
}
});
} else {
return Observable.just(queryAndStops.second);
}
}
)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<List<Stop>>bindToLifecycle())
.subscribe(new Action1<List<Stop>>() {
#Override
public void call(List<Stop> stops) {
// since I don't know what your API is returning I think
// it's saver to keep this check in:
if (stops != null) {
searchStopResultFragment.showStops(stops);
} else {
searchStopResultFragment.showStartItems();
}
}
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
showError(throwable);
}
});
where:
public static final Func1<EditText, String> EXTRACT_STRING = new Func1<EditText, String>() {
#Override
public void String call(EditText editText) {
return editText.getText().toString();
}
};
public static final Func1<String, Boolean> STRING_IS_NOT_EMPTY = new Func1<String, Boolean>() {
#Override
public void String call(String string) {
return !string.isEmpty();
}
};
So, this at least removes the need to return Observable.just(null) and then check for that down the chain.
You can move your second concatMap to the only place you need it - after combineLatest
RxUtils.createEditTextChangeObservable(txtInput)
.throttleLast(200, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.concatMap(new Func1<EditText, Observable<Pair<String, List<Stop>>>>() {
#Override
public Observable<Pair<String, List<Stop>>> call(EditText editText) {
String query = editText.getText().toString();
//searchStopResultFragment.setLastQuery(query);
if (query.isEmpty()) {
return Observable.just(null);
}
return Observable
.combineLatest(StopProvider.getStopResultObservable(getContentResolver(), query), Observable.just(query), new Func2<List<Stop>, String, Pair<String, List<Stop>>>() {
#Override
public Pair<String, List<Stop>> call(List<Stop> stops, String s) {
return new Pair(s, stops);
}
})
.concatMap(new Func1<R, Observable<? extends Pair<String, List<Stop>>>>() {
#Override
public Observable<? extends Pair<String, List<Stop>>> call(R r) {
if (queryAndStops.second.size() == 0) {
return RestClient.service().locationName(queryAndStops.first).concatMap(new Func1<LocationNameResponse, Observable<? extends List<Stop>>>() {
#Override
public Observable<? extends List<Stop>> call(LocationNameResponse locationNameResponse) {
return Observable.just(locationNameResponse.getAddresses());
}
});
} else {
return Observable.just(queryAndStops.second);
}
}
});
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread()).compose(this.<List<Stop>>bindToLifecycle())
.subscribe(new Action1<List<Stop>>() {
#Override
public void call(List<Stop> stops) {
if (stops != null) {
searchStopResultFragment.showStops(stops);
} else {
searchStopResultFragment.showStartItems();
}
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
showError(throwable);
}
});
Solved it using concatmap and combine latest:
RxUtils.createEditTextChangeObservable(txtInput).throttleLast(200, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()).concatMap(new Func1<EditText, Observable<Pair<String,List<Stop>>>>() {
#Override
public Observable<Pair<String, List<Stop>>> call(EditText editText) {
String query = editText.getText().toString();
//searchStopResultFragment.setLastQuery(query);
if(query.isEmpty()){
return Observable.just(null);
}
return Observable.combineLatest(StopProvider.getStopResultObservable(getContentResolver(), query), Observable.just(query), new Func2<List<Stop>, String, Pair<String, List<Stop>>>() {
#Override
public Pair<String, List<Stop>> call(List<Stop> stops, String s) {
return new Pair(s,stops);
}
});
}
}).concatMap(new Func1<Pair<String, List<Stop>>, Observable<List<Stop>>>() {
#Override
public Observable<List<Stop>> call(Pair<String, List<Stop>> queryAndStops) {
if(queryAndStops!=null) {
if (queryAndStops.second.size() == 0) {
return RestClient.service().locationName(queryAndStops.first).concatMap(new Func1<LocationNameResponse, Observable<? extends List<Stop>>>() {
#Override
public Observable<? extends List<Stop>> call(LocationNameResponse locationNameResponse) {
return Observable.just(locationNameResponse.getAddresses());
}
});
} else {
return Observable.just(queryAndStops.second);
}
}
return Observable.just(null);
}
}).subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread()).compose(this.<List<Stop>>bindToLifecycle()).subscribe(new Action1<List<Stop>>() {
#Override
public void call(List<Stop> stops) {
if (stops != null) {
searchStopResultFragment.showStops(stops);
}else{
searchStopResultFragment.showStartItems();
}
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
showError(throwable);
}
});
However is there some nicer way to break out of the chain without sending Observable.just(null) and check for nulls in next call?