Exoplayer2 in recyclerview video pause on notifyitemchange is called - android

I have the task to make video play in recyclerview use exoplayer
and use this reference :
I am follow tutorial by Droid wafe :
and code works very well in my application. but. there is a slight problem. the problem is when I call the notifyItemChange (position) function to update an item on videoViewHolder. the video paused, but the audio still runs in the background.
I think the problem is when notifyItemChange is called, video view holder in re-create and make the video pause (default) but audio keep running cause not calling setplaywhenready(false)
import android.content.Context;
import android.graphics.Point;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.RequestManager;
import com.google.android.exoplayer2.*;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.*;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
import es.hyrax.zonapets.R;
import es.hyrax.zonapets.data.network.model.post.Post;
import es.hyrax.zonapets.ui.user_private.home.dashboard_my_zona.viewholder_myzona.ViewHolderImages;
import es.hyrax.zonapets.ui.user_private.home.dashboard_my_zona.viewholder_myzona.ViewHolderMultipleImages;
import es.hyrax.zonapets.ui.user_private.home.dashboard_my_zona.viewholder_myzona.ViewHolderVideos;
import es.hyrax.zonapets.utils.listener.OnDoubleClickListener;
import java.util.ArrayList;
import java.util.Objects;
public class ExoPlayerRecyclerViewMyZona extends RecyclerView {
private static final String TAG = "ExoPlayerRecyclerView";
private static final String AppName = "ZonaPets";
/**
* PlayerViewHolder UI component
* Watch PlayerViewHolder class
*/
private ImageView mediaCoverImage, volumeControl;
private FrameLayout container_volume;
private ProgressBar progressBar;
private View viewHolderParent;
private AspectRatioFrameLayout mediaContainer;
private PlayerView videoSurfaceView;
private SimpleExoPlayer videoPlayer;
/**
* variable declaration
*/
// Media List
private ArrayList<Post> mediaObjects = new ArrayList<>();
private int videoSurfaceDefaultHeight = 0;
private int screenDefaultHeight = 0;
private Context context;
private int playPosition = -1;
private boolean isVideoViewAdded;
private RequestManager requestManager;
// controlling volume state
private VolumeState volumeState;
private int targetPosition;
private OnEventClickVideoViewHolder onEventClick;
public ExoPlayerRecyclerViewMyZona(#NonNull Context context) {
super(context);
init(context);
}
public ExoPlayerRecyclerViewMyZona(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
this.context = context.getApplicationContext();
Display display = ((WindowManager) Objects.requireNonNull(
getContext().getSystemService(Context.WINDOW_SERVICE))).getDefaultDisplay();
Point point = new Point();
display.getSize(point);
videoSurfaceDefaultHeight = point.x;
screenDefaultHeight = point.y;
videoSurfaceView = new PlayerView(this.context);
videoSurfaceView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_ZOOM);
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector =
new DefaultTrackSelector(videoTrackSelectionFactory);
//Create the player using ExoPlayerFactory
videoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector);
// Disable Player Control
videoSurfaceView.setUseController(false);
// Bind the player to the containerView.
videoSurfaceView.setPlayer(videoPlayer);
// Turn on Volume
setVolumeControl(VolumeState.ON);
addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (mediaCoverImage != null) {
// show the old thumbnail
mediaCoverImage.setVisibility(VISIBLE);
}
// There's a special case when the end of the list has been reached.
// Need to handle that with this bit of logic
if (!recyclerView.canScrollVertically(1)) {
playVideo(true);
} else {
playVideo(false);
}
}
}
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
addOnChildAttachStateChangeListener(new OnChildAttachStateChangeListener() {
#Override
public void onChildViewAttachedToWindow(#NonNull View view) {
}
#Override
public void onChildViewDetachedFromWindow(#NonNull View view) {
if (viewHolderParent != null && viewHolderParent.equals(view)) {
resetVideoView();
}
}
});
videoPlayer.addListener(new Player.EventListener() {
#Override
public void onTimelineChanged(Timeline timeline, #Nullable Object manifest, int reason) {
}
#Override
public void onTracksChanged(TrackGroupArray trackGroups,
TrackSelectionArray trackSelections) {
}
#Override
public void onLoadingChanged(boolean isLoading) {
}
#Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
switch (playbackState) {
case Player.STATE_BUFFERING:
Log.e(TAG, "onPlayerStateChanged: Buffering video.");
if (progressBar != null) {
progressBar.setVisibility(VISIBLE);
}
if (volumeControl != null){
volumeControl.setAlpha(1f);
}
break;
case Player.STATE_ENDED:
Log.d(TAG, "onPlayerStateChanged: Video ended.");
videoPlayer.seekTo(0);
break;
case Player.STATE_IDLE:
break;
case Player.STATE_READY:
Log.e(TAG, "onPlayerStateChanged: Ready to play.");
if (progressBar != null) {
progressBar.setVisibility(GONE);
}
if (volumeControl != null){
volumeControl.setAlpha(1f);
}
if (!isVideoViewAdded) {
addVideoView();
}
break;
default:
break;
}
}
#Override
public void onRepeatModeChanged(int repeatMode) {
}
#Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
}
#Override
public void onPlayerError(ExoPlaybackException error) {
}
#Override
public void onPositionDiscontinuity(int reason) {
}
#Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
}
#Override
public void onSeekProcessed() {
}
});
}
public void playVideo(boolean isEndOfList) {
int targetPosition;
if (!isEndOfList) {
int startPosition = ((LinearLayoutManager) Objects.requireNonNull(
getLayoutManager())).findFirstVisibleItemPosition();
int endPosition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
// if there is more than 2 list-items on the screen, set the difference to be 1
if (endPosition - startPosition > 1) {
endPosition = startPosition + 1;
}
// something is wrong. return.
if (startPosition < 0 || endPosition < 0) {
return;
}
// if there is more than 1 list-item on the screen
if (startPosition != endPosition) {
int startPositionVideoHeight = getVisibleVideoSurfaceHeight(startPosition);
int endPositionVideoHeight = getVisibleVideoSurfaceHeight(endPosition);
targetPosition =
startPositionVideoHeight > endPositionVideoHeight ? startPosition : endPosition;
} else {
targetPosition = startPosition;
}
} else {
targetPosition = mediaObjects.size() - 1;
}
Log.d(TAG, "playVideo: target position: " + targetPosition);
// video is already playing so return
if (targetPosition == playPosition) {
return;
}
// set the position of the list-item that is to be played
playPosition = targetPosition;
if (videoSurfaceView == null) {
return;
}
// remove any old surface views from previously playing videos
videoSurfaceView.setVisibility(INVISIBLE);
removeVideoView(videoSurfaceView);
//set get target position to this target position
this.targetPosition = targetPosition;
int currentPosition =
targetPosition - ((LinearLayoutManager) Objects.requireNonNull(
getLayoutManager())).findFirstVisibleItemPosition();
View child = getChildAt(currentPosition);
if (child == null) {
return;
}
if (child.getTag() instanceof ViewHolderVideos) {
ViewHolderVideos holder = (ViewHolderVideos) child.getTag();
if (holder == null) {
playPosition = -1;
return;
}
mediaCoverImage = holder.img_post;
progressBar = holder.progressBar;
volumeControl = holder.volumeControl;
viewHolderParent = holder.itemView;
container_volume = holder.container_volume;
requestManager = holder.requestManager;
mediaContainer = holder.mediaContainer;
videoSurfaceView.setPlayer(videoPlayer);
mediaContainer.setOnClickListener(new OnDoubleClickListener() {
#Override
public void onDoubleClick(View v) {
onEventClick.onDoubleClickListener(targetPosition);
}
#Override
public void onSingleClick(View v) {
onEventClick.onSingleClickListener(targetPosition);
}
});
container_volume.setOnClickListener(new OnDoubleClickListener() {
#Override
public void onDoubleClick(View v) {
}
#Override
public void onSingleClick(View v) {
toggleVolume();
}
});
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(
context, Util.getUserAgent(context, AppName));
String mediaUrl = mediaObjects.get(targetPosition).getImagepost().get(0).getVideo();/*"https://androidwave.com/media/androidwave-video-1.mp4";*/
if (mediaUrl != null) {
MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.parse(mediaUrl));
videoPlayer.prepare(videoSource);
videoPlayer.setPlayWhenReady(true);
}
}else {
videoPlayer.setPlayWhenReady(false);
}
}
/**
* Returns the visible region of the video surface on the screen.
* if some is cut off, it will return less than the #videoSurfaceDefaultHeight
*/
private int getVisibleVideoSurfaceHeight(int playPosition) {
int at = playPosition - ((LinearLayoutManager) Objects.requireNonNull(
getLayoutManager())).findFirstVisibleItemPosition();
Log.d(TAG, "getVisibleVideoSurfaceHeight: at: " + at);
View child = getChildAt(at);
if (child == null) {
return 0;
}
int[] location = new int[2];
child.getLocationInWindow(location);
if (location[1] < 0) {
return location[1] + videoSurfaceDefaultHeight;
} else {
return screenDefaultHeight - location[1];
}
}
// Remove the old player
private void removeVideoView(PlayerView videoView) {
ViewGroup parent = (ViewGroup) videoView.getParent();
if (parent == null) {
return;
}
int index = parent.indexOfChild(videoView);
if (index >= 0) {
parent.removeViewAt(index);
isVideoViewAdded = false;
//viewHolderParent.setOnClickListener(null);
}
}
private void addVideoView() {
mediaContainer.addView(videoSurfaceView);
isVideoViewAdded = true;
videoSurfaceView.requestFocus();
videoSurfaceView.setVisibility(VISIBLE);
videoSurfaceView.setAlpha(1);
mediaCoverImage.setVisibility(GONE);
volumeControl.setVisibility(VISIBLE);
}
private void resetVideoView() {
if (isVideoViewAdded) {
removeVideoView(videoSurfaceView);
playPosition = -1;
videoSurfaceView.setVisibility(INVISIBLE);
mediaCoverImage.setVisibility(VISIBLE);
volumeControl.setVisibility(VISIBLE);
}
}
private Boolean currentPositionIsVideoType(){
int currentPosition =
targetPosition - ((LinearLayoutManager) Objects.requireNonNull(
getLayoutManager())).findFirstVisibleItemPosition();
View child = getChildAt(currentPosition);
if (child == null) {
return false;
}
if (child.getTag() instanceof ViewHolderVideos) {
return true;
}else {
return false;
}
}
public void releasePlayer() {
if (currentPositionIsVideoType()) {
if (videoPlayer != null) {
videoPlayer.release();
videoPlayer = null;
}
viewHolderParent = null;
}
}
public void onPausePlayer() {
if (currentPositionIsVideoType()){
if (videoPlayer != null) {
//videoPlayer.stop(true);
videoPlayer.setPlayWhenReady(false);
videoPlayer.getPlaybackState();
}
}
}
public void resumePlayer() {
if (currentPositionIsVideoType()){
if (videoPlayer != null) {
videoPlayer.setPlayWhenReady(true);
videoPlayer.getPlaybackState();
}
}
}
private void toggleVolume() {
if (videoPlayer != null) {
if (volumeState == VolumeState.OFF) {
Log.d(TAG, "togglePlaybackState: enabling volume.");
setVolumeControl(VolumeState.ON);
} else if (volumeState == VolumeState.ON) {
Log.d(TAG, "togglePlaybackState: disabling volume.");
setVolumeControl(VolumeState.OFF);
}
}
}
//public void onRestartPlayer() {
// if (videoPlayer != null) {
// playVideo(true);
// }
//}
private void setVolumeControl(VolumeState state) {
volumeState = state;
if (state == VolumeState.OFF) {
videoPlayer.setVolume(0f);
animateVolumeControl();
} else if (state == VolumeState.ON) {
videoPlayer.setVolume(1f);
animateVolumeControl();
}
}
private void animateVolumeControl() {
if (volumeControl != null) {
volumeControl.bringToFront();
if (volumeState == VolumeState.OFF) {
requestManager.load(R.drawable.ic_volume_off)
.into(volumeControl);
} else if (volumeState == VolumeState.ON) {
requestManager.load(R.drawable.ic_volume_on)
.into(volumeControl);
}
//volumeControl.animate().cancel();
volumeControl.setAlpha(1f);
volumeControl.animate()
.alpha(1f)
.setDuration(600).setStartDelay(1000);
}
}
public void setMediaObjects(ArrayList<Post> mediaObjects) {
this.mediaObjects = mediaObjects;
}
public void setVideoViewHolderClickListener(OnEventClickVideoViewHolder onEventClick){
this.onEventClick = onEventClick;
}
/**
* Volume ENUM
*/
private enum VolumeState {
ON, OFF
}
public interface OnEventClickVideoViewHolder {
void onSingleClickListener(int position);
void onDoubleClickListener(int position);
}
}
and log cat not show any error!!
please take a look my comment in issue report for help understand

Related

How to get the result from EventObserver in Android Java

I am following this post: https://stackoverflow.com/questions/56071990/android-architecture-singleliveevent-and-eventobserver-practicle-example-in-java
public class Event<T> {
private boolean hasBeenHandled = false;
private T content;
public Event(T content) {
this.content = content;
}
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return content;
}
}
public boolean isHandled() {
return hasBeenHandled;
}
}
import androidx.annotation.Nullable;
import androidx.lifecycle.Observer;
public class EventObserver<T> implements Observer<Event<T>> {
private OnEventChanged onEventChanged;
public EventObserver(OnEventChanged onEventChanged) {
this.onEventChanged = onEventChanged;
}
#Override
public void onChanged(#Nullable Event<T> tEvent) {
if (tEvent != null && tEvent.getContentIfNotHandled() != null && onEventChanged != null)
onEventChanged.onUnhandledContent(tEvent.getContentIfNotHandled());
}
interface OnEventChanged<T> {
void onUnhandledContent(T data);
}
}
I have this in my repository:
private MutableLiveData<Event<String>> _amsRepositoryError = new MutableLiveData<>();
public MutableLiveData<Event<UserModel>> amsLoginSuccessReply(){return _amsLoginSuccessReply;}
_amsLoginSuccessReply.postValue(new Event(userModel));
And I catch this in my viewmodel:
amsRepository.amsLoginSuccessReply().observe(mLifeCycleOwner, new EventObserver<UserModel>(data -> {
// HOW DO I GET THE data here.
}));
on the observe, how do I get the values of the data?

Obserable status

Hello I have this program :
That after a input data should show button bellow but obserable doesnt work at all
I have made 3 statuses each for input and if all of them is true I should show button (another status)
How could i display a button after input in all fields. If you need more code please tell me
package com.example.android_lab1;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements LifecycleOwner {
Button btn;
MainActivityViewModel model;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
model = new ViewModelProvider(this).get(MainActivityViewModel.class);
btn = findViewById(R.id.check_btn);
EditText i_imie = findViewById(R.id.input_imie);
EditText i_nazwisko = findViewById(R.id.input_nazwisko);
EditText i_l_ocen = findViewById(R.id.input_l_ocen);
i_imie.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
if (i_imie.getText().toString().isEmpty()) {
Toast.makeText(v.getContext(), "Imie nie moze byc puste", Toast.LENGTH_SHORT).show();
model.setImie_status(false);
}else{
model.setImie_status(true);
}
}
}
});
i_nazwisko.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
if (i_nazwisko.getText().toString().isEmpty()) {
Toast.makeText(v.getContext(), "Nazwisko nie moze byc puste", Toast.LENGTH_SHORT).show();
model.setNazwisko_status(false);
}else{
model.setNazwisko_status(true);
}
}
}
});
i_l_ocen.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
if (i_l_ocen.getText().toString().isEmpty()) {
Toast.makeText(v.getContext(), "Liczba ocen nie moze byc pusta", Toast.LENGTH_SHORT).show();
} else {
int x = Integer.parseInt(i_l_ocen.getText().toString());
if (x < 5 || x > 15) {
model.setL_ocen_status(false);
Toast.makeText(v.getContext(), "Liczba ocen musi byc w przedziale 5 -15", Toast.LENGTH_SHORT).show();
}else{
model.setL_ocen_status(true);
}
}
}
}
});
model.getImie().observe(this, string -> {
i_imie.setText(string);
if(!string.isEmpty()){
model.setImie_status(true);
}
});
model.getNazwisko().observe(this, string -> {
i_nazwisko.setText(string);
if(!string.isEmpty()){
model.setNazwisko_status(true);}
});
model.getLOcen().observe(this, integer -> {
i_l_ocen.setText(integer.toString());
if(integer>=5 && integer<=15){
model.setL_ocen_status(true);
}
});
model.getBtn_status().observe(this, aBoolean -> {
if(aBoolean == false){
btn.setVisibility(View.INVISIBLE);
}else if(aBoolean ==true){
btn.setVisibility(View.VISIBLE);
}
});
btn.setOnClickListener(v -> {
checkData(i_imie, i_nazwisko, i_l_ocen);
});
}
void checkData(EditText e_imie, EditText e_nazwisko, EditText e_l_ocen) {
String imie = e_imie.getText().toString();
String nazwisko = e_nazwisko.getText().toString();
if (imie.isEmpty() || nazwisko.isEmpty() || e_l_ocen.getText().toString().isEmpty()) {
if (imie.isEmpty()) {
e_imie.setError("Imie nie moze byc puste");
}
if (nazwisko.isEmpty()) {
e_nazwisko.setError("Nazwisko nie moze byc puste");
}
if (e_l_ocen.getText().toString().isEmpty()) {
e_l_ocen.setError("Liczba ocen nie moze byc pusta");
}
} else {
if (Integer.parseInt(e_l_ocen.getText().toString()) < 5 || Integer.parseInt(e_l_ocen.getText().toString()) > 15) {
e_l_ocen.setError("Liczba ocen musi byc w zakresie 5-15");
} else {
saveData(imie,nazwisko, Integer.parseInt( e_l_ocen.getText().toString()));
}
}
}
private void saveData(String imie, String nazwisko, Integer l_ocen) {
model.setImie(imie);
model.setNazwisko(nazwisko);
model.setL_ocen(l_ocen);
model.setNazwisko_status(true);
}
}
ViewModel:
package com.example.android_lab1;
import android.view.View;
import android.widget.Button;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class MainActivityViewModel extends ViewModel {
private MutableLiveData<String> imie;
private MutableLiveData<String> nazwisko;
private MutableLiveData<Integer> l_ocen;
private MutableLiveData<Boolean> imie_status, nazwisko_status, l_ocen_status, btn_status;
public MainActivityViewModel() {
imie = new MutableLiveData<>();
nazwisko= new MutableLiveData<>();
l_ocen = new MutableLiveData<>();
imie_status = new MutableLiveData<>(false);
nazwisko_status = new MutableLiveData<>(false);
l_ocen_status = new MutableLiveData<>(false);
btn_status = new MutableLiveData<>();
}
LiveData<String> getImie(){
return imie;
}
LiveData<String> getNazwisko(){
return nazwisko;
}
LiveData<Integer> getLOcen(){
return l_ocen;
}
LiveData<Boolean> getBtn_status(){
if(imie_status.getValue() ==true || nazwisko_status.getValue() == true || l_ocen_status.getValue()==true){
btn_status.setValue(true);
}else{
btn_status.setValue(false);
}
return btn_status;
}
public void setImie(String imie) {
this.imie.setValue(imie);
}
public void setNazwisko(String nazwisko) {
this.nazwisko.setValue(nazwisko);
}
public void setL_ocen(Integer l_ocen) {
this.l_ocen.setValue(l_ocen);
}
public void setImie_status(Boolean btn_status){
this.imie_status.setValue(btn_status);
}
public void setNazwisko_status(Boolean btn_status){
this.nazwisko_status.setValue(btn_status);
}
public void setL_ocen_status(Boolean btn_status){
this.l_ocen_status.setValue(btn_status);
}
public void setBtn_status(Boolean btn_status){
this.btn_status.setValue(btn_status);
}
}
In your view model you should create a method to update btn_status, and call after each status is set:
private void updateBtnStatus(){
btn_status.setValue(imie_status.getValue() || nazwisko_status.getValue() || l_ocen_status.getValue());
}
public void setImie_status(Boolean btn_status){
this.imie_status.setValue(btn_status);
updateBtnStatus();
}
public void setNazwisko_status(Boolean btn_status){
this.nazwisko_status.setValue(btn_status);
updateBtnStatus();
}
public void setL_ocen_status(Boolean btn_status){
this.l_ocen_status.setValue(btn_status);
updateBtnStatus();
}
Also, you should consider to use addTextChangedListener instead of setOnFocusChangeListener to avoid to have to focus another field after filled the last one:
i_l_ocen.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) {
}
});

Configure ItemTouchHelper in RecyclerView to work for Snackbar being dismissed by another Snackbar being displayed

I have a RecyclerView in my app that contains messages. On swiping right, I've configured it to delete the messages and on swiping left, to archive the messages. Here's the code for the ItemTouchHelper part related to deleting and archiving:
if (direction == ItemTouchHelper.LEFT) {
donorMessagesAdapter.deleteMessages(position);
} else if (direction == ItemTouchHelper.RIGHT) {
donorMessagesAdapter.archiveMessages(position);
}
Here are the deleteMessages() and archiveMessages() functions (they're almost the same):
public void deleteMessages(final int position) {
recentlyDeletedPeer = sortedPeers.get(position);
recentlyDeletedPeerPosition = position;
sortedPeers.remove(position);
notifyItemRemoved(position);
Snackbar snackbar = Snackbar.make(activity.findViewById(R.id.constraint_layout),
"Messages Deleted", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener() {
#Override
public void onClick(View view) {
sortedPeers.add(recentlyDeletedPeerPosition, recentlyDeletedPeer);
notifyItemInserted(recentlyDeletedPeerPosition);
}
});
snackbar.setActionTextColor(0xff0099ff);
snackbar.addCallback(new Snackbar.Callback() {
#Override
public void onDismissed(Snackbar snackbar, int event) {
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT || event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE) {
MainActivity.database.receivedMessagesDao().deleteAllReceivedMessagesFromSender(email, recentlyDeletedPeer);
MainActivity.database.sentMessagesDao().deleteAllSentMessagesFromReceiver(email, recentlyDeletedPeer);
}
}
});
snackbar.show();
}
public void archiveMessages(final int position) {
recentlyArchivedPeer = sortedPeers.get(position);
recentlyArchivedPeerPosition = position;
sortedPeers.remove(position);
notifyItemRemoved(position);
Snackbar snackbar = Snackbar.make(activity.findViewById(R.id.constraint_layout),
"Messages archived", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener() {
#Override
public void onClick(View view) {
sortedPeers.add(recentlyArchivedPeerPosition, recentlyArchivedPeer);
notifyItemInserted(recentlyArchivedPeerPosition);
}
});
snackbar.setActionTextColor(0xff0099ff);
snackbar.addCallback(new Snackbar.Callback() {
#Override
public void onDismissed(Snackbar snackbar, int event) {
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT || event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE) {
MainActivity.database.receivedMessagesDao().archive(email, recentlyArchivedPeer);
MainActivity.database.sentMessagesDao().archive(email, recentlyArchivedPeer);
}
}
});
snackbar.show();
}
Everything works perfectly well when deleting/archiving one message. Swiping deletes or archives the message after the Snackbar disappears, and when I click undo, the deletion/archival is reversed. Problems arise when deleting/archiving multiple messages.
Problem 1: When I delete/archive multiple messages without waiting for the Snackbar to disappear for each message, all except the first message is deleted/archived. What it makes it stranger for me is that it's not that only the last message is deleted/archived, all except the first are deleted/archived.
Problem 2: When I delete/archive multiple messages without waiting for the Snackbar to disappear for each message, if I press UNDO, that is, on the Snackbar for the last message that was deleted/archived, the app crashes.
By the way,I'm using Room Library to store the messages in a database (referenced by MainActivity.database).
I think the problem is in the addCallback() function for the Snackbar. I'm supposed to do something differently for event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE from event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT, but I can't figure out exactly what I'm supposed to do differently, and what exactly causes the problem.
Here is my entire adapter class for reference (error message lines marked with comment next to them):
package com.example.treeapp;
import android.app.Activity;
import android.content.Intent;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar;
import com.mikhaellopez.circularimageview.CircularImageView;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class DonorMessagesAdapter extends RecyclerView.Adapter<DonorMessagesAdapter.DonorMessagesViewHolder> { // line 24 in error message
private Activity activity;
public String email;
public List<String> sortedPeers;
public static CircularImageView imageView;
private String recentlyDeletedPeer;
private int recentlyDeletedPeerPosition;
private String recentlyArchivedPeer;
private int recentlyArchivedPeerPosition;
public static class DonorMessagesViewHolder extends RecyclerView.ViewHolder {
public Button middleButton;
public TextView nameTextView;
public TextView messageTextView;
DonorMessagesViewHolder(View view) {
super(view);
middleButton = view.findViewById(R.id.middle_button);
imageView = view.findViewById(R.id.image_view);
nameTextView = view.findViewById(R.id.text_view_name);
messageTextView = view.findViewById(R.id.text_view_message);
}
}
DonorMessagesAdapter(Activity activity, String email) {
this.activity = activity;
this.email = email;
this.sortedPeers = getSortedPeers(email);
}
#NonNull
#Override
public DonorMessagesViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.message, parent, false);
return new DonorMessagesViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull DonorMessagesViewHolder holder, int position) {
final String current = sortedPeers.get(position);
// Start of getting last message
String lastMessage;
String lastMessageType;
List<Integer> receivedMessages = MainActivity.database.receivedMessagesDao().getMessagesToReceiverFromSender(email, current, 0);
List<Integer> sentMessages = MainActivity.database.sentMessagesDao().getMessagesBySenderToReceiver(email, current, 0);
if (sentMessages.size() == 0) {
lastMessage = MainActivity.database.receivedMessagesDao().getMessage(receivedMessages.get(receivedMessages.size() - 1)); // line 74 in error message
lastMessageType = "received";
if (MainActivity.database.receivedMessagesDao().getImage(receivedMessages.get(receivedMessages.size() - 1)) == 1) {
lastMessageType = "receivedImage";
}
} else if (receivedMessages.size() == 0) {
lastMessage = MainActivity.database.sentMessagesDao().getMessage(sentMessages.get(sentMessages.size() - 1));
lastMessageType = "sent";
if (MainActivity.database.sentMessagesDao().getImage(sentMessages.get(sentMessages.size() - 1)) == 1) {
lastMessageType = "sentImage";
}
} else {
LocalDateTime lastReceivedMessageTime = LocalDateTime.parse(MainActivity.database.receivedMessagesDao().getTime(receivedMessages.get(receivedMessages.size() - 1)));
LocalDateTime lastSentMessageTime = LocalDateTime.parse(MainActivity.database.sentMessagesDao().getTime(sentMessages.get(sentMessages.size() - 1)));
if (lastReceivedMessageTime.isAfter(lastSentMessageTime)) {
lastMessage = MainActivity.database.receivedMessagesDao().getMessage(receivedMessages.get(receivedMessages.size() - 1));
lastMessageType = "received";
if (MainActivity.database.receivedMessagesDao().getImage(receivedMessages.get(receivedMessages.size() - 1)) == 1) {
lastMessageType = "receivedImage";
}
} else {
lastMessage = MainActivity.database.sentMessagesDao().getMessage(sentMessages.get(sentMessages.size() - 1));
lastMessageType = "sent";
if (MainActivity.database.sentMessagesDao().getImage(sentMessages.get(sentMessages.size() - 1)) == 1) {
lastMessageType = "sentImage";
}
}
}
// End of getting last message
if (MainActivity.database.plantersDao().checkEmail(current) == 1) {
if (MainActivity.database.plantersDao().getImageUrl(current) != null) {
new DownloadImageTask(imageView).execute(MainActivity.database.plantersDao().getImageUrl(current));
}
} else if (MainActivity.database.donorsDao().checkEmail(current) == 1) {
if (MainActivity.database.donorsDao().getImageUrl(current) != null) {
new DownloadImageTask(imageView).execute(MainActivity.database.donorsDao().getImageUrl(current));
}
}
if (MainActivity.database.plantersDao().checkEmail(current) == 1) {
holder.nameTextView.setText(MainActivity.database.plantersDao().getName(current));
} else if (MainActivity.database.donorsDao().checkEmail(current) == 1) {
holder.nameTextView.setText(MainActivity.database.donorsDao().getName(current));
}
if (lastMessageType.equals("received")) {
if (lastMessage.length() > 17) {
holder.messageTextView.setText(lastMessage.substring(0, 16) + "...");
} else {
holder.messageTextView.setText(lastMessage);
}
} else if (lastMessageType.equals("sent")) {
if (lastMessage.length() > 12) {
holder.messageTextView.setText("You: " + lastMessage.substring(0, 11) + "...");
} else {
holder.messageTextView.setText("You: " + lastMessage);
}
} else if (lastMessageType.equals("receivedImage")) {
holder.messageTextView.setText("Image");
} else if (lastMessageType.equals("sentImage")) {
holder.messageTextView.setText("You: " + "Image");
}
holder.middleButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(view.getContext(), MessageActivity.class);
intent.putExtra("email", email);
intent.putExtra("peerEmail", current);
intent.putExtra("archived", 0);
view.getContext().startActivity(intent);
}
});
}
#Override
public int getItemCount() {
return sortedPeers.size();
}
public void reload() {
sortedPeers = getSortedPeers(email);
notifyDataSetChanged();
}
private List<String> getSortedPeers(String email) {
List<String> senders = MainActivity.database.receivedMessagesDao().getSendersForReceiver(email, 0);
List<String> receivers = MainActivity.database.sentMessagesDao().getReceiversForSender(email, 0);
List<String> peers = new ArrayList<>();
peers.addAll(senders);
peers.addAll(receivers);
peers = new ArrayList<>(new HashSet<>(peers));
List<String> sortedPeers = new ArrayList<>();
while (peers.size() > 0) {
LocalDateTime latestMessageTime = null;
String latestPeer = null;
for (String peer : peers) {
List<Integer> receivedMessages = MainActivity.database.receivedMessagesDao().getMessagesToReceiverFromSender(email, peer, 0);
List<Integer> sentMessages = MainActivity.database.sentMessagesDao().getMessagesBySenderToReceiver(email, peer, 0);
LocalDateTime lastMessageTime;
if (sentMessages.size() == 0) {
lastMessageTime = LocalDateTime.parse(MainActivity.database.receivedMessagesDao().getTime(receivedMessages.get(receivedMessages.size() - 1)));
} else if (receivedMessages.size() == 0) {
lastMessageTime = LocalDateTime.parse(MainActivity.database.sentMessagesDao().getTime(sentMessages.get(sentMessages.size() - 1)));
} else {
LocalDateTime lastReceivedMessageTime = LocalDateTime.parse(MainActivity.database.receivedMessagesDao().getTime(receivedMessages.get(receivedMessages.size() - 1)));
LocalDateTime lastSentMessageTime = LocalDateTime.parse(MainActivity.database.sentMessagesDao().getTime(sentMessages.get(sentMessages.size() - 1)));
if (lastReceivedMessageTime.isAfter(lastSentMessageTime)) {
lastMessageTime = lastReceivedMessageTime;
} else {
lastMessageTime = lastSentMessageTime;
}
}
if (latestMessageTime != null) {
if (lastMessageTime.isAfter(latestMessageTime)) {
latestMessageTime = lastMessageTime;
latestPeer = peer;
}
} else {
latestMessageTime = lastMessageTime;
latestPeer = peer;
}
}
sortedPeers.add(latestPeer);
peers.remove(latestPeer);
}
return sortedPeers;
}
public void deleteMessages(final int position) {
recentlyDeletedPeer = sortedPeers.get(position);
recentlyDeletedPeerPosition = position;
sortedPeers.remove(position);
notifyItemRemoved(position);
Snackbar snackbar = Snackbar.make(activity.findViewById(R.id.constraint_layout),
"Messages Deleted", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener() {
#Override
public void onClick(View view) {
sortedPeers.add(recentlyDeletedPeerPosition, recentlyDeletedPeer);
notifyItemInserted(recentlyDeletedPeerPosition);
}
});
snackbar.setActionTextColor(0xff0099ff);
snackbar.addCallback(new Snackbar.Callback() {
#Override
public void onDismissed(Snackbar snackbar, int event) {
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT || event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE) {
MainActivity.database.receivedMessagesDao().deleteAllReceivedMessagesFromSender(email, recentlyDeletedPeer);
MainActivity.database.sentMessagesDao().deleteAllSentMessagesFromReceiver(email, recentlyDeletedPeer);
}
}
});
snackbar.show();
}
public void archiveMessages(final int position) {
recentlyArchivedPeer = sortedPeers.get(position);
recentlyArchivedPeerPosition = position;
sortedPeers.remove(position);
notifyItemRemoved(position);
Snackbar snackbar = Snackbar.make(activity.findViewById(R.id.constraint_layout),
"Messages archived", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener() {
#Override
public void onClick(View view) {
sortedPeers.add(recentlyArchivedPeerPosition, recentlyArchivedPeer);
notifyItemInserted(recentlyArchivedPeerPosition);
}
});
snackbar.setActionTextColor(0xff0099ff);
snackbar.addCallback(new Snackbar.Callback() {
#Override
public void onDismissed(Snackbar snackbar, int event) {
if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT || event == Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE) {
MainActivity.database.receivedMessagesDao().archive(email, recentlyArchivedPeer);
MainActivity.database.sentMessagesDao().archive(email, recentlyArchivedPeer);
reload();
}
}
});
snackbar.show();
}
}
And here is the error I get (my classes marked with **):
2020-09-12 19:24:11.547 689-689/com.example.treeapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.treeapp, PID: 689
java.lang.ArrayIndexOutOfBoundsException: length=0; index=-1
at java.util.ArrayList.get(ArrayList.java:439)
**at com.example.treeapp.DonorMessagesAdapter.onBindViewHolder(DonorMessagesAdapter.java:74)
at com.example.treeapp.DonorMessagesAdapter.onBindViewHolder(DonorMessagesAdapter.java:24)**
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7065)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7107)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6012)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6279)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1762)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1544)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:955)
at android.view.View.layout(View.java:20967)
at android.view.ViewGroup.layout(ViewGroup.java:6440)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3092)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2779)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1863)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8072)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
at android.view.Choreographer.doCallbacks(Choreographer.java:723)
at android.view.Choreographer.doFrame(Choreographer.java:658)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
To solve your problem no.1 you could try to only check if the dismiss was caused by a click e.g.:
snackbar.addCallback(new Snackbar.Callback() {
#Override
public void onDismissed(Snackbar snackbar, int event) {
if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {
MainActivity.database.receivedMessagesDao().deleteAllReceivedMessagesFromSender(email, recentlyDeletedPeer);
MainActivity.database.sentMessagesDao().deleteAllSentMessagesFromReceiver(email, recentlyDeletedPeer);
}
}
});
As for your second problem, the crash happens because you only check if the sentMessages list is empty
if (sentMessages.size() == 0) {
lastMessage = MainActivity.database.receivedMessagesDao().getMessage(receivedMessages.get(receivedMessages.size() - 1)); // line 74 in error message
lastMessageType = "received";
if (MainActivity.database.receivedMessagesDao().getImage(receivedMessages.get(receivedMessages.size() - 1)) == 1) {
lastMessageType = "receivedImage";
}
}
What you forget to do here is to check whether reveivedMessages is also empty.
For example:
if (sentMessages.size() == 0) {
if(receivedMessages.size() != 0) {
lastMessage = MainActivity.database.receivedMessagesDao().getMessage(receivedMessages.get(receivedMessages.size() - 1)); // line 74 in error message
lastMessageType = "received";
if (MainActivity.database.receivedMessagesDao().getImage(receivedMessages.get(receivedMessages.size() - 1)) == 1) {
lastMessageType = "receivedImage";
}
}
}
What happens is your receivedMessages list is also empty which means you try to get the item at position -1, which obviously wont work.
Even if these tips solve your problem you should seriously think about how you access your data in the database, because you seem to execute all of the calls on the UI Thread, which is not advisable since it will block the UI Thread.

Mopub: integration with the Millennial Media native ads adapter

Does anyone know how to integrate the Millennial Media native ads adapter?
I read this page: https://support.millennialmedia.com/hc/en-us/articles/205081010-MoPub-Integrations
Downloaded the android adapters and then replace the APID with my APID, but don't know what the DCN is and I was unable to find it on their site.
I think I'm getting the error below because I have not inputted the DCN:
03-24 22:07:12.832 29491-29491/? D/MoPub: Attempting to invoke custom
event: com.mopub.nativeads.MillennialNative 03-24 22:07:12.850
29491-29491/? V/MoPub: Native Ad failed to load with error:
CustomEventNative was configured incorrectly.. 03-24 22:07:13.328
29491-29491/? D/MoPub: Attempting to invoke custom event:
com.mopub.nativeads.MoPubCustomEventNative
This is their adapter I'm using the Millennial Media:
package com.mopub.nativeads;
import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import com.millennialmedia.AppInfo;
import com.millennialmedia.MMException;
import com.millennialmedia.MMSDK;
import com.millennialmedia.NativeAd;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static com.mopub.nativeads.NativeImageHelper.preCacheImages;
public class MillennialNative extends CustomEventNative {
public static final String DCN_KEY = "dcn";
public static final String APID_KEY = "XXX";
private final static String LOGCAT_TAG = "MoPub->MM-Native";
private static final Handler UI_THREAD_HANDLER = new Handler(Looper.getMainLooper());
public String siteId;
#Override
protected void loadNativeAd(final Activity activity,
final CustomEventNativeListener listener,
final Map<String, Object> localExtras,
final Map<String, String> serverExtras) {
String placementId;
if ( !MMSDK.isInitialized() ) {
try {
MMSDK.initialize(activity);
} catch ( Exception e ) {
Log.e(LOGCAT_TAG, "Unable to initialize the Millennial SDK-- " + e.getMessage());
e.printStackTrace();
UI_THREAD_HANDLER.post(new Runnable() {
#Override
public void run() {
listener.onNativeAdFailed(NativeErrorCode.NATIVE_ADAPTER_CONFIGURATION_ERROR);
}
});
return;
}
}
if ( extrasAreValid( serverExtras )) {
placementId = serverExtras.get(APID_KEY);
siteId = serverExtras.get(DCN_KEY);
} else {
UI_THREAD_HANDLER.post(new Runnable() {
#Override
public void run() {
listener.onNativeAdFailed(NativeErrorCode.NATIVE_ADAPTER_CONFIGURATION_ERROR);
}
});
return;
}
try {
AppInfo ai = new AppInfo().setMediator("mopubsdk");
if ( siteId != null && siteId.length() > 0 ) {
ai = ai.setSiteId(siteId);
} else {
ai = ai.setSiteId(null);
}
MMSDK.setAppInfo(ai);
} catch ( IllegalStateException e ) {
Log.w(LOGCAT_TAG, "Caught exception: " + e.getMessage());
UI_THREAD_HANDLER.post(new Runnable() {
#Override
public void run() {
listener.onNativeAdFailed(NativeErrorCode.NATIVE_ADAPTER_CONFIGURATION_ERROR);
}
});
return;
}
try {
NativeAd nativeAd = NativeAd.createInstance(placementId, NativeAd.NATIVE_TYPE_INLINE);
final MillennialStaticNativeAd millennialStaticNativeAd =
new MillennialStaticNativeAd(activity,
nativeAd,
new ImpressionTracker(activity),
new NativeClickHandler(activity),
listener);
millennialStaticNativeAd.loadAd();
} catch ( MMException e ) {
UI_THREAD_HANDLER.post(new Runnable() {
#Override
public void run() {
listener.onNativeAdFailed(NativeErrorCode.NATIVE_ADAPTER_CONFIGURATION_ERROR);
}
});
}
}
private boolean extrasAreValid(final Map<String, String> serverExtras) {
String placementId = serverExtras.get(APID_KEY);
return (serverExtras.containsKey(APID_KEY) &&
placementId != null && placementId.length() > 0 );
}
static class MillennialStaticNativeAd extends StaticNativeAd implements NativeAd.NativeListener {
private final Context mContext;
private NativeAd mNativeAd;
private final ImpressionTracker mImpressionTracker;
private final NativeClickHandler mNativeClickHandler;
private final CustomEventNativeListener mListener;
private final MillennialStaticNativeAd mMillennialStaticNativeAd;
public MillennialStaticNativeAd(final Context context,
final NativeAd nativeAd,
final ImpressionTracker impressionTracker,
final NativeClickHandler nativeClickHandler,
final CustomEventNativeListener customEventNativeListener) {
mContext = context.getApplicationContext();
mNativeAd = nativeAd;
mImpressionTracker = impressionTracker;
mNativeClickHandler = nativeClickHandler;
mListener = customEventNativeListener;
mMillennialStaticNativeAd = this;
nativeAd.setListener(this);
}
void loadAd() {
Log.i(LOGCAT_TAG, "Loading native ad...");
try {
mNativeAd.load(mContext, null);
} catch (MMException e) {
Log.w(MillennialNative.LOGCAT_TAG, "Caught configuration error Exception.");
e.printStackTrace();
UI_THREAD_HANDLER.post(new Runnable() {
#Override
public void run() {
mListener.onNativeAdFailed(NativeErrorCode
.NATIVE_ADAPTER_CONFIGURATION_ERROR);
}
});
}
}
// Lifecycle Handlers
#Override
public void prepare(final View view) {
// Must access these methods directly to get impressions to fire.
mNativeAd.getIconImage();
mNativeAd.getDisclaimer();
mImpressionTracker.addView(view, this);
mNativeClickHandler.setOnClickListener(view, this);
}
#Override
public void clear(final View view) {
mImpressionTracker.removeView(view);
mNativeClickHandler.clearOnClickListener(view);
}
#Override
public void destroy() {
mImpressionTracker.destroy();
mNativeAd.setListener(null);
mNativeAd = null;
}
// Event Handlers
#Override
public void recordImpression(final View view) {
notifyAdImpressed();
try {
mNativeAd.fireImpression();
Log.i(LOGCAT_TAG, "Millennial native impression recorded.");
} catch ( MMException m ) {
Log.e(LOGCAT_TAG, "Millennial native impression NOT tracked: " + m.getMessage() );
}
}
#Override
public void handleClick(final View view) {
notifyAdClicked();
mNativeClickHandler.openClickDestinationUrl(getClickDestinationUrl(), view);
mNativeAd.fireClicked();
Log.i(LOGCAT_TAG, "Millennial native ad clicked!");
}
// MM'S Native mListener
#Override
public void onLoaded(NativeAd nativeAd) {
// Set assets
String iconImageUrl = nativeAd.getImageUrl(NativeAd.ComponentName.ICON_IMAGE, 1);
String mainImageUrl = nativeAd.getImageUrl(NativeAd.ComponentName.MAIN_IMAGE, 1);
setTitle(nativeAd.getTitle().getText().toString());
setText(nativeAd.getBody().getText().toString());
setCallToAction(nativeAd.getCallToActionButton().getText().toString());
final String clickDestinationUrl = nativeAd.getCallToActionUrl();
if (clickDestinationUrl == null) {
UI_THREAD_HANDLER.post(new Runnable() {
#Override
public void run() {
Log.d(LOGCAT_TAG,
"Millennial native encountered null destination url. Failing over.");
mListener.onNativeAdFailed(
NativeErrorCode.NATIVE_ADAPTER_CONFIGURATION_ERROR);
}
});
return;
}
setClickDestinationUrl(clickDestinationUrl);
setIconImageUrl(iconImageUrl);
setMainImageUrl(mainImageUrl);
final List<String> urls = new ArrayList<String>();
if ( iconImageUrl != null ) { urls.add(iconImageUrl); }
if ( mainImageUrl != null ) { urls.add(mainImageUrl); }
UI_THREAD_HANDLER.post(new Runnable() {
#Override
public void run() {
// This has to be run on the main thread:
preCacheImages(mContext, urls, new NativeImageHelper.ImageListener() {
#Override
public void onImagesCached() {
mListener.onNativeAdLoaded(mMillennialStaticNativeAd);
Log.i(LOGCAT_TAG, "Millennial native ad loaded");
}
#Override
public void onImagesFailedToCache(NativeErrorCode errorCode) {
mListener.onNativeAdFailed(errorCode);
}
});
}
});
}
#Override
public void onLoadFailed(NativeAd nativeAd, NativeAd.NativeErrorStatus nativeErrorStatus) {
final NativeErrorCode error;
switch ( nativeErrorStatus.getErrorCode() ) {
case NativeAd.NativeErrorStatus.LOAD_TIMED_OUT:
error = NativeErrorCode.NETWORK_TIMEOUT;
break;
case NativeAd.NativeErrorStatus.NO_NETWORK:
error = NativeErrorCode.CONNECTION_ERROR;
break;
case NativeAd.NativeErrorStatus.UNKNOWN:
error = NativeErrorCode.UNSPECIFIED;
break;
case NativeAd.NativeErrorStatus.LOAD_FAILED:
case NativeAd.NativeErrorStatus.INIT_FAILED:
error = NativeErrorCode.UNEXPECTED_RESPONSE_CODE;
break;
case NativeAd.NativeErrorStatus.ADAPTER_NOT_FOUND:
error = NativeErrorCode.NATIVE_ADAPTER_CONFIGURATION_ERROR;
break;
case NativeAd.NativeErrorStatus.DISPLAY_FAILED:
case NativeAd.NativeErrorStatus.EXPIRED:
error = NativeErrorCode.UNSPECIFIED;
break;
default:
error = NativeErrorCode.NETWORK_NO_FILL;
}
UI_THREAD_HANDLER.post(new Runnable() {
#Override
public void run() {
mListener.onNativeAdFailed(error);
}
});
Log.i(LOGCAT_TAG, "Millennial native ad failed: " + nativeErrorStatus.getDescription() );
}
#Override
public void onClicked(NativeAd nativeAd, NativeAd.ComponentName componentName, int i) {
Log.i(LOGCAT_TAG, "Millennial native SDK's click tracker fired.");
}
#Override
public void onAdLeftApplication(NativeAd nativeAd) {
Log.i(LOGCAT_TAG, "Millennial native SDK has left the application.");
}
#Override
public void onExpired(NativeAd nativeAd) {
Log.i(LOGCAT_TAG, "Millennial native ad has expired!");
}
}
}

Why main thread can't update UI elements inside a horizontallistview?

Last few days I'm trying to update some UI Views inside a custom HorizontalListView. I've been trying countless way like using Async Task, posting message via handler etc. I didn't figure out to update them. As a result, I tried to build a queue system to update them one by one. Here is my code and it's still not updating them.
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import roboguice.RoboGuice;
import CustomChannel;
import EGPInfo;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import android.widget.TextView;
public class EPGLoader {
static private EPGLoader _instance;
public static EPGLoader getInstance(Context context) {
if (_instance == null) {
_instance = new EPGLoader();
}
mContext = context;
return _instance;
}
private HashMap<String, WeakReference<EPGChannel>> _cachedChannels;
private Queue<EPGChannel> _queue;
private EPGThread _thread;
private static Context mContext;
private boolean _isBusy;
public EPGLoader() {
_cachedChannels = new HashMap<String, WeakReference<EPGChannel>>();
_queue = new LinkedList<EPGChannel>();
_isBusy = false;
}
public void load(ArrayList<TextView> textViews, CustomChannel channel) {
Iterator<EPGChannel> it = _queue.iterator();
EPGChannel epgChannel = new EPGChannel();
epgChannel.setChannel(channel);
epgChannel.setTextViews(textViews);
while (it.hasNext()) {
if (it.next().equals(epgChannel)) {
it.remove();
break;
}
}
_queue.add(epgChannel);
loadNext();
}
private void loadNext() {
Iterator<EPGChannel> it = _queue.iterator();
if (!_isBusy && it.hasNext()) {
_isBusy = true;
EPGChannel epgChannel = it.next();
it.remove();
EPGChannel epgCache = get(epgChannel.getChannel().getChannelId());
if (epgCache != null) {
Log.i("A", "Cache");
epgChannel.textViews.get(0).setText("1");
epgChannel.textViews.get(1).setText("2");
epgChannel.textViews.get(2).setText("3");
epgChannel.textViews.get(3).setText("4");
_isBusy = false;
loadNext();
} else {
_thread = new EPGThread(epgChannel);
_thread.start();
Log.i("A", "Live");
}
}
}
private EPGChannel get(String channelId) {
if (_cachedChannels.containsKey(channelId)) {
return _cachedChannels.get(channelId).get();
} else {
return null;
}
}
private class EPGThread extends Thread {
private EPGChannel EPGChannel;
final Handler threadHandler = new Handler();
final Runnable threadCallBack = new Runnable() {
#Override
public void run() {
onLoad();
}
};
public EPGThread(EPGChannel epgChannel) {
this.EPGChannel = epgChannel;
}
private void onLoad() {
if (_thread != null) {
EPGChannel epgChannel = _thread.EPGChannel;
// epgChannel.getTextViews().get(1).setText(epgChannel.getChannel().getName());
epgChannel.getTextViews().get(0).setText("1");
epgChannel.getTextViews().get(1).setText("2");
epgChannel.getTextViews().get(2).setText("3");
epgChannel.getTextViews().get(3).setText("4");
}
_thread = null;
_isBusy = false;
loadNext();
}
#Override
public void run() {
try {
_isBusy = true;
ChannelManager channelManager = RoboGuice.getInjector(mContext).getInstance(ChannelManager.class);
ArrayList<EGPInfo> epgInfo = channelManager.getChannelEPG(EPGChannel.getChannel().getChannelId());
if (epgInfo.size() == 2) {
EPGChannel.getChannel().updateEPGInformations(epgInfo.get(0), epgInfo.get(1));
}
if (!_cachedChannels.containsKey(EPGChannel.getChannel().getChannelId()))
_cachedChannels.put(EPGChannel.getChannel().getChannelId(), new WeakReference<EPGLoader.EPGChannel>(EPGChannel));
} catch (Exception e) {
e.printStackTrace();
} finally {
threadHandler.post(threadCallBack);
}
}
}
private class EPGChannel {
private ArrayList<TextView> textViews;
private CustomChannel channel;
public ArrayList<TextView> getTextViews() {
return textViews;
}
public void setTextViews(ArrayList<TextView> textViews) {
this.textViews = textViews;
}
public CustomChannel getChannel() {
return channel;
}
public void setChannel(CustomChannel channel) {
this.channel = channel;
}
}
}
I call it from main thread when getView event of ArrayAdapter fires.
#Override
public View getView(int position, View retval, ViewGroup parent) {
if (retval == null) {
retval = LayoutInflater.from(parent.getContext()).inflate(R.layout.channel_item, null);
}
final CustomChannel currentChannel = getItem(position);
final TextView nowTitle = (TextView) retval.findViewById(R.id.channel_item_current_show_title);
final TextView nowPlayTime = (TextView) retval.findViewById(R.id.channel_item_current_show_play_time);
final TextView nextTitle = (TextView) retval.findViewById(R.id.channel_item_next_show_title);
final TextView nextPlayTime = (TextView) retval.findViewById(R.id.channel_item_next_show_play_time);
ArrayList<TextView> textViews = new ArrayList<TextView>();
textViews.add(nowTitle);
textViews.add(nowPlayTime);
textViews.add(nextTitle);
textViews.add(nextPlayTime);
EPGLoader.getInstance(mContext).load(textViews, currentChannel);
return retval;
}
So, what is wrong with my codes? Thanks in advance.
Edit: I replace the HorizontalListView with normal Android's ListView and it works but I have to use HorizontalListView in the project. Any suggesstions?
Edit 2: I reward to back (HorizontalListView) and tried to set Background color of some UI controllers, guess what? It updated. It doesn't update text property!!! Why????

Categories

Resources