i m using leak canary to detect memory leak and i must say it is very good library to detect potential memory leak below is the leak report produce by it.i am not able to find how to remove this anonymous implementation.
GoProFragment has leaked:
GC ROOT java.util.Timer$TimerImpl.
references MainActivity$5.this$0 (anonymous subclass of java.util.TimerTask)
references MainActivity.mHelper
references IabHelper.mPurchaseListener
references GoProFragment$2.this$0 (anonymous implementation of IabHelper$OnIabPurchaseFinishedListener)
GoProFragment instance
**GoProFragment.java**
public class GoProFragment extends BaseFragment {
#Bind(R.id.btn_subscription_yearly)
Button btnsubScriptionYearly;
#Bind(R.id.btn_subscription_monthly)
Button monthly;
private ProgressDialog progressDialog;
String product_price_one, product_price_two, product_price_three;
#Bind(R.id.subscription_type)
TextView subScriptionType;
#Bind(R.id.graph_paid_feature)
ImageView imageView;
Bitmap icon;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.pro_layout, container, false);
ButterKnife.bind(this, view);
setHasOptionsMenu(true);
activity.preferences.setDisplayUpgrade(false);
icon = BitmapFactory.decodeResource(activity.getResources(), R.drawable.subscription_img_two);
imageView.setImageBitmap(icon);
subScriptionType.setText(activity.preferences.getSubscriptionType());
String[] sku = { Util.SKU_CHALLENGE, Util.SKU_SUBSCRIPTION, Util.SKU_YEAR_SUBSCRIPTION};
if(isNetworkAvailable(activity)) {
activity.mHelper.queryInventoryAsync(true, Arrays.asList(sku), mGotInventoryListener);
showProgressDialog();
}
if (activity.preferences.isHasSubscription()) {
/*btnsubScriptionYearly.setVisibility(View.GONE);
monthly.setVisibility(View.GONE);*/
}
return view;
}
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
#Override
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
dismissProgressDialog();
product_price_one = inventory.getSkuDetails( Util.SKU_SUBSCRIPTION).getPrice();
product_price_two = inventory.getSkuDetails( Util.SKU_YEAR_SUBSCRIPTION).getPrice();
product_price_three = inventory.getSkuDetails( Util.SKU_CHALLENGE).getPrice();
monthly.setVisibility(View.VISIBLE);
btnsubScriptionYearly.setVisibility(View.VISIBLE);
monthly.setText(activity.getString(R.string.monthly_subscription_text, product_price_one));
btnsubScriptionYearly.setText(activity.getString(R.string.yearly_subscription_text, product_price_two));
}
};
#OnClick(R.id.btn_subscription_monthly)
public void onSubscription() {
if (Validator.isNotNull(activity.mHelper)) {
if (!activity.mHelper.subscriptionsSupported()) {
Util.showToast(activity, getString(R.string.subscription_not_supported));
return;
}
if (!activity.mHelper.getAsyncInProgress()) {
activity.mHelper.launchPurchaseFlow(activity,
Util.SKU_SUBSCRIPTION, IabHelper.ITEM_TYPE_SUBS,
Util.RC_REQUEST, mPurchaseFinishedListener, Util.PAYLOAD);
}
}
}
#OnClick(R.id.btn_subscription_yearly)
public void onYearSubscription() {
if (Validator.isNotNull(activity.mHelper)) {
if (!activity.mHelper.subscriptionsSupported()) {
Util.showToast(activity, getString(R.string.subscription_not_supported));
return;
}
if (!activity.mHelper.getAsyncInProgress()) {
activity.mHelper.launchPurchaseFlow(activity,
Util.SKU_YEAR_SUBSCRIPTION, IabHelper.ITEM_TYPE_SUBS,
Util.RC_REQUEST, mPurchaseFinishedListener, Util.PAYLOAD);
}
}
}
private boolean isNetworkAvailable(Context context) {
final ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity != null) {
NetworkInfo[] info = connectivity.getAllNetworkInfo();
if (info != null)
for (int i = 0; i < info.length; i++)
if (info[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
return false;
}
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
// if we were disposed of in the meantime, quit.
if (activity.mHelper == null) {
return;
}
if (result.isFailure()) {
return;
}
if (! Util.verifyDeveloperPayload(purchase)) {
return;
}
if (purchase.getSku().equals( Util.SKU_SUBSCRIPTION)) {
// bought the infinite gas subscription
setPremium(purchase, Calendar.MONTH);
} else if (purchase.getSku().equals( Util.SKU_YEAR_SUBSCRIPTION)) {
// bought the infinite gas subscription
setPremium(purchase, Calendar.YEAR);
}
}
};
public void setPremium(Purchase purchase, int duration) {
if ( Util.verifyDeveloperPayload(purchase)) {
activity.preferences.setHasSubscription(true);
if (Validator.isNotNull(activity.adView)) {
activity.adView.setVisibility(View.GONE);
}
Util.showToast(activity, getString(R.string.upgraded_successfully));
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date(purchase.getPurchaseTime()));
calendar.add(duration, 1);
activity.preferences.getUserInfo().setSubscriptionEndOn(calendar.getTime());
if(purchase.getSku().equals( Util.SKU_SUBSCRIPTION))
{
activity.preferences.setSubscriptionType(activity.getResources().getString(R.string.monthly));
}
else {
activity.preferences.setSubscriptionType(activity.getResources().getString(R.string.yearly));
}
activity.preferences.getUserInfo().setPremium(true);
//subscriptionEndOn.setText(dateFormat.format(activity.preferences.getUserInfo().getSubscriptionEndOn()));
}
}
private void showProgressDialog() {
if (!activity.isFinishing() && progressDialog == null) {
progressDialog = new ProgressDialog(activity,R.style.CustomProgressDialog);
progressDialog.setCancelable(false);
progressDialog.show();
}
}
/**
* dismiss Progress Dialog.
*/
private void dismissProgressDialog() {
if (!activity.isFinishing() && progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
progressDialog=null;
}
}
#Override
public void onDetach() {
if(Validator.isNotNull(icon)){
icon.recycle();
}
super.onDetach();
}
I don't think it is a problem. You can hold onto state in a Fragment but you should call setRetainInstance(true); in the onCreate callback.
See this article
Alternatively you could set the field mGotInventoryListener = null; in the onDetach callback.
Related
I am creating an android tv app in the app I am sliding infinity loop fragments (Image and Video URLs ). I use glide for image loading and exoplayer2 for video loading it's working perfectly in my emulator and other 2-3 devices but when I run it in my actual android tv it keeps crashing after some time with an error memory exception. I tested it In emulator using profiler how much memory it using but it is only using min 100 Mb not bigger than that I even check for a leak using leak cannery no leak was found I can't find any solution can someone please help me
Fragment:
`public class BannerFragment extends Fragment {
/*TODO TAGs*/
private String TAG = BannerFragment.class.getSimpleName();
/*TODO View Binding*/
private BannerFragmentBinding vb;
private PromotionalBanner promotionalBanner;
private int position;
private ArrayList<PromotionalBanner> promotionalBannerArrayList;
private CountDownTimer timer;
SimpleExoPlayer simpleExoPlayer;
private final String VIDEO_BUFFER_TIMER = "10000";
/*TODO Glide Listener*/
private RequestListener requestListener = new RequestListener<Drawable>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
if (vb != null) {
ShowToast(" Can't load image ");
vb.avi.setVisibility(View.GONE);
slideNextItem();
}
return false;
}
#Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
if (vb != null) {
vb.avi.setVisibility(View.GONE);
}
return false;
}
};
/*TODO PlayerListener*/
private Player.Listener playerListener = new Player.Listener() {
#Override
public void onIsPlayingChanged(boolean isPlaying) {
Log.e(TAG, "onIsPlayingChanged: " + isPlaying);
}
#Override
public void onPlaybackStateChanged(int playbackState) {
if (playbackState == Player.STATE_ENDED) {
slideNextItem();
} else if (playbackState == Player.STATE_READY) {
vb.avi.setVisibility(View.GONE);
if (timer != null) {
timer.cancel();
timer = null;
}
Log.e("TimerCheck", "onPlaybackStateChanged: endTimer "+timer );
} else if (playbackState == Player.STATE_BUFFERING) {
vb.avi.setVisibility(View.VISIBLE);
setSlideTimer(VIDEO_BUFFER_TIMER);
Log.e("TimerCheck", "onPlaybackStateChanged: startTimer "+timer );
} else if (playbackState == Player.STATE_IDLE) {
slideNextItem();
}
}
#Override
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
}
};
/*TODO get Fragment Instant*/
public static Fragment getInstantFragment(PromotionalBanner promotionalBanner,
int index,
ArrayList<PromotionalBanner> promotionalBannerArrayList) {
BannerFragment fragment = new BannerFragment();
Bundle bundle = new Bundle();
bundle.putParcelable("promotionalBanner", promotionalBanner);
bundle.putInt("position", index);
bundle.putParcelableArrayList("list", promotionalBannerArrayList);
fragment.setArguments(bundle);
return fragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.promotionalBanner = getArguments().getParcelable("promotionalBanner");
this.position = getArguments().getInt("position");
this.promotionalBannerArrayList = getArguments().getParcelableArrayList("list");
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
vb = BannerFragmentBinding.inflate(getLayoutInflater());
return vb.getRoot();
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
#Override
public void onResume() {
super.onResume();
if (!isImageFile(promotionalBanner.getMedia())) {
showVideoViewContainer();
setVideo(promotionalBanner);
} else {
hideVideoViewContainer();
loadImage(promotionalBanner.getMedia(), promotionalBanner.getDuration());
/*TODo set image Timer*/
setSlideTimer(promotionalBanner.getDuration());
}
}
/*TODO setVideo*/
private void setVideo(PromotionalBanner promotionalBanner) {
Uri uri = Uri.parse(promotionalBanner.getMedia());
Log.e("dataUriTest", "setVideo: " + uri);
simplePlayer(uri);
}
/*TODO Simple Player*/
private void simplePlayer(Uri uri) {
simpleExoPlayer = new SimpleExoPlayer.Builder(getContext()).build();
simpleExoPlayer.addMediaItem(MediaItem.fromUri(uri));
setSlideTimer(VIDEO_BUFFER_TIMER);
Log.e("TimerCheck", "onPlaybackStateChanged:insideSimplePlayer startTimer "+timer );
vb.avi.setVisibility(View.VISIBLE);
vb.videoView.hideController();
vb.videoView.setPlayer(simpleExoPlayer);
simpleExoPlayer.prepare();
simpleExoPlayer.setPlayWhenReady(true);
simpleExoPlayer.addListener(playerListener);
}
/*TODO Go To next From vide Slide*/
private void slideNextItem() {
if(simpleExoPlayer != null){
simpleExoPlayer.release();
simpleExoPlayer = null;
}
int nextposition = position + 1;
goToNextItem(nextposition);
}
#Override
public void onPause() {
super.onPause();
Log.e("LogBannerFragment", "onPause: ");
if (timer != null) {
timer.cancel();
}
if (simpleExoPlayer != null) {
simpleExoPlayer.release();
simpleExoPlayer = null;
}
}
#Override
public void onDestroy() {
super.onDestroy();
Log.e("Destory", "onDestroy: " );
if (playerListener != null) {
playerListener = null;
}
if (requestListener != null) {
requestListener = null;
}
if (vb != null) {
vb = null;
}
}
#Override
public void onDestroyView() {
super.onDestroyView();
}
/*TODO load Image*/
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void loadImage(String media, String duration) {
vb.avi.setVisibility(View.VISIBLE);
Glide.with(getContext()).load(convert_http_https(media))
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.error(getContext().getDrawable(R.drawable.banner))
.addListener(requestListener)
.into(vb.imageView);
}
/*TODO convert url http to https in android studio*/
private String convert_http_https(String url) {
String original;
if (url.contains("https")) {
original = url;
} else {
original = url.replace("http", "https");
}
return original;
}
/*TODO set Image timer*/
private void setSlideTimer(String duration) {
timer = new CountDownTimer(Integer.parseInt(duration), 1000) {
#Override
public void onTick(long millisUntilFinished) {
}
#Override
public void onFinish() {
int nextPosition = position + 1;
// int nextPosition = DispolyActivity.vb.viewPager.getCurrentItem()+1;
goToNextItem(nextPosition);
}
};
timer.start();
}
/*TODO Next Position in arrayList*/
private void goToNextItem(int nextPosition) {
HandlerCompat.createAsync(Looper.getMainLooper())
.post(() ->
DispolyActivity.vb.viewPager.setCurrentItem(nextPosition)
);
}
/*TODO is image file*/
public static boolean isImageFile(String path) {
String mimeType = URLConnection.guessContentTypeFromName(path);
return mimeType != null && mimeType.startsWith("image");
}
/*TODO Video view Container*/
private void showVideoViewContainer() {
Log.e("datashow", "showVideoViewContainer: "+vb);
vb.videoContainer.setVisibility(View.VISIBLE);
vb.imageView.setVisibility(View.GONE);
}
/*TODO hide video view Container*/
private void hideVideoViewContainer() {
vb.videoContainer.setVisibility(View.GONE);
vb.imageView.setVisibility(View.VISIBLE);
}
/*TODO show Toast*/
private void ShowToast(String msg) {
Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show();
}
}
`
My Adapter for slide fragment :
public class SliderAdapter extends FragmentStatePagerAdapter {
private String TAG = SliderAdapter.class.getSimpleName();
private ArrayList<PromotionalBanner> promotionalBannerArrayList;
public SliderAdapter(#NonNull FragmentManager fm,ArrayList<PromotionalBanner> promotionalBannerArrayList) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
this.promotionalBannerArrayList = promotionalBannerArrayList;
}
#NonNull
#Override
public Fragment getItem(int position) {
PromotionalBanner promotionalBanner = DispolyActivity.promotionalBannerArrayList.get(position);
Log.e("positiondata", "dataPost: "+position );
return BannerFragment.getInstantFragment(promotionalBanner,
position,DispolyActivity.promotionalBannerArrayList);
}
#Override
public int getCount() {
return DispolyActivity.promotionalBannerArrayList.size();
}
#Override
public int getItemPosition(#NonNull Object object) {
return POSITION_UNCHANGED;
}
#Override
public void destroyItem(#NonNull ViewGroup container,
int position, #NonNull Object object) {
super.destroyItem(container, position, object);
Log.e("itemDestroy", "destroyItem: "+object );
}
/*TODO update Adapter Data*/
public void updateAdapter_Data(ArrayList<PromotionalBanner> promotionalBannersList){
this.promotionalBannerArrayList = promotionalBannersList;
// notifyDataSetChanged();
}
}
When working with the player, you need to carefully monitor the life cycle.
simpleExoPlayer.release();
simpleExoPlayer = null;
Try to use this not only in onPause(), try it also in onDestroy and onDestroyView.
If the problem goes away, then go through which life cycle methods are called and leave the release call in the right places
You have an issue not with memory, most likely you have an issue related to the codec.
Each device has its own limited number of codecs.
Some devices have more, some have less.
Therefore, on some devices, you can run the video more times, especially on emulators.
(sorry, I don't know if my English is right, I hope it is right!)
loadInBackground() method,(in BookLoader class)returns a string value, but to who?
I looked for who calls loadInBackground() too but nobody does it.I read the official documentations too but without solutions.Thanks in advice friends.
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<String> {
public EditText mEditText;
public TextView mTextTitle, mTextAuthor;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mEditText = (EditText) findViewById(R.id.bookInput);
mTextTitle = (TextView) findViewById(R.id.titleText);
mTextAuthor = (TextView) findViewById(R.id.authorText);
// to reconnect to the Loader if it already exists
if(getSupportLoaderManager().getLoader(0)!=null){
getSupportLoaderManager().initLoader(0,null,this);
}
}
public void searchBooks(View view) {
String queryString = mEditText.getText().toString();
// nasconde la tastiera
InputMethodManager inputManager = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
// Check the status of the network connection.
ConnectivityManager connMgr = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected() && queryString.length()!=0) {
mTextAuthor.setText("");
mTextTitle.setText(R.string.loading);
Bundle queryBundle = new Bundle();
queryBundle.putString("queryString", queryString);
getSupportLoaderManager().restartLoader(0, queryBundle,this);
}
else {
if (queryString.length() == 0) {
mTextAuthor.setText("");
mTextTitle.setText("Please enter a search term");
} else {
mTextAuthor.setText("");
mTextTitle.setText("Please check your network connection and try again.");
}
}
}
// Called when you instantiate your Loader.
#NonNull
#Override
public Loader<String> onCreateLoader(int i, #Nullable Bundle bundle) {
return new BookLoader(this, bundle.getString("queryString"));
}
#Override
public void onLoadFinished(#NonNull Loader<String> loader, String s) {
try {
JSONObject jsonObject = new JSONObject(s);
JSONArray itemsArray = jsonObject.getJSONArray("items");
int i = 0;
String title = null;
String authors = null;
while (i < itemsArray.length() || (authors == null && title == null)) {
// Get the current item information.
JSONObject book = itemsArray.getJSONObject(i);
JSONObject volumeInfo = book.getJSONObject("volumeInfo");
// Try to get the author and title from the current item,
// catch if either field is empty and move on.
try {
title = volumeInfo.getString("title");
authors = volumeInfo.getString("authors");
Log.d("TITLE", volumeInfo.getString("title"));
} catch (Exception e){
e.printStackTrace();
}
// Move to the next item.
i++;
}
// If both are found, display the result.
if (title != null && authors != null){
mTextTitle.setText(title);
mTextAuthor.setText(authors);
mEditText.setText("");
} else {
// If none are found, update the UI to show failed results.
mTextTitle.setText("no results");
mTextAuthor.setText("");
}
} catch (Exception e){
// If onPostExecute does not receive a proper JSON string, update the UI to show failed results.
mTextTitle.setText("no results");
mTextAuthor.setText("");
e.printStackTrace();
}
}
// Cleans up any remaining resources.
#Override
public void onLoaderReset(#NonNull Loader<String> loader) {
}
}
public class BookLoader extends AsyncTaskLoader<String> {
String mQueryString;
public BookLoader(#NonNull Context context, String queryString) {
super(context);
mQueryString = queryString;
}
#Nullable
#Override
public String loadInBackground() {
return NetworkUtils.getBookInfo(mQueryString);
}
#Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad();
}
}
I edited the post.
As the official documentation said:
onPostExecute(Result), invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter.
Your return value is send to onPostExecute().
I encourage you to have a deeper look at this documentation
I have a switch that when you click it it populates a RecyclerView and trying to save the state through the lifecycle.
This is the xml
<Switch
android:id="#+id/reviewLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/eight_dp"
android:textColor="#android:color/white" />
This is the listener
private class ShowReviewsListener implements CompoundButton.OnCheckedChangeListener{
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
if(isChecked == true){
showReviews();
isReviewButtonClicked = true;
}else if(isChecked == false){
isReviewButtonClicked = false;
}
}
}
This is what happens when you click it
public void showReviews() {
mReviewList.setHasFixedSize(true);
mReviewList.setVisibility(View.VISIBLE);
fakeView2.setVisibility(View.VISIBLE);
}
This is how i try to save it and retrieve it
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt(INSTANCE_MOVIE_ID, mMovieId);
outState.putBoolean(IS_IN_FAVORITES, isInFavsAlready);
outState.putBoolean(REVIEW_BUTTON, isReviewButtonClicked);
super.onSaveInstanceState(outState);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_movie_detail);
mDb = AppDatabase.getInstance(getApplicationContext());
mToolbar = findViewById(R.id.toolbar);
mToolbar.setTitle(R.string.movie_details_title);
ButterKnife.bind(this);
if (savedInstanceState != null && savedInstanceState.containsKey(INSTANCE_MOVIE_ID)) {
mMovieId = savedInstanceState.getInt(INSTANCE_MOVIE_ID, DEFAULT_MOVIE_ID);
}
if(savedInstanceState !=null && savedInstanceState.containsKey(IS_IN_FAVORITES)){
isInFavsAlready = savedInstanceState.getBoolean(IS_IN_FAVORITES, false);
}
if(savedInstanceState !=null && savedInstanceState.containsKey(REVIEW_BUTTON)){
isReviewButtonClicked = savedInstanceState.getBoolean(REVIEW_BUTTON, false);
}
Log.d(LOG_TAG, "review button " + isReviewButtonClicked);
Intent i = getIntent();
if (i != null && i.hasExtra(EXTRA_MOVIE)) {
if (mMovieId == DEFAULT_MOVIE_ID) {
mMovieId = i.getIntExtra(EXTRA_MOVIE, DEFAULT_MOVIE_ID);
mMovie = i.getParcelableExtra(EXTRA_MOVIE);
populateUI(mMovie);
}
}
setTrailers();
setReviews();
if (isReviewButtonClicked) {
showReviews();
}
int movieID = Integer.parseInt(mMovie.getMovieId());
isMovieInFavorites(movieID);
reviewSwitch.setOnCheckedChangeListener(new ShowReviewsListener());
favoriteToggle.setOnCheckedChangeListener(new FavoriteListener());
}
Right now even though the isChecked is true, whenever i rotate the device, the views from showReviews() are staying hidden.
EDIT: Added full onCreate & image
Reviews handle
private class FetchReviewsAndTrailersTask extends AsyncTask<URL, Void, String[]> {
#Override
protected String[] doInBackground(URL... urls) {
URL searchReviewUrl = NetworkUtils.createReviewsUrl(mMovie.getMovieId());
URL searchVideoUrl = NetworkUtils.createVideosUrl(mMovie.getMovieId());
String jsonReviewString = "";
String jsonVideoString = "";
try {
jsonReviewString = NetworkUtils.makeHttpRequest(searchReviewUrl);
jsonVideoString = NetworkUtils.makeHttpRequest(searchVideoUrl);
} catch (IOException e) {
Log.e("Main Activity", "Problem making the HTTP request.", e);
}
return new String[]{jsonVideoString, jsonReviewString};
}
#Override
protected void onPostExecute(String[] jsonString) {
if (jsonString == null) {
fakeView.setVisibility(View.VISIBLE);
}
mTrailers = JsonUtils.extractTrailersFromJson(jsonString[0]);
mReviews = JsonUtils.extractReviewsFromJson(jsonString[1]);
populateReviewsAndTrailers(mReviews, mTrailers);
}
}
private void populateReviewsAndTrailers(List<Review> review, List<Trailer> trailers){
if (review.isEmpty()) {
reviewSwitch.setText(R.string.reviewLabelNone);
} else {
reviewSwitch.setText(R.string.reviewLabelExist);
fakeView.setVisibility(View.GONE);
mAdapter = new MovieReviewsRecyclerViewAdapter(MovieDetailActivity.this, mReviews);
mReviewList.addItemDecoration(new DividerItemDecoration(getApplicationContext(), DividerItemDecoration.VERTICAL));
mReviewList.setAdapter(mAdapter);
mReviewList.setVisibility(View.GONE);
}
if(trailers.isEmpty()){
trailersHeader.setText(R.string.trailersNA);
}else{
trailersHeader.setText(R.string.trailerHeader);
mTrailerAdapter = new MovieTrailersRecyclerViewAdapter(MovieDetailActivity.this, mTrailers);
mTrailersList.setAdapter(mTrailerAdapter);
}
}
I guess you forget to show reviews after rotating screen.
Try this:
if(savedInstanceState !=null && savedInstanceState.containsKey(REVIEW_BUTTON)){
isReviewButtonClicked = savedInstanceState.getBoolean(REVIEW_BUTTON, false);
if (isReviewButtonClicked) showReviews();
}
I have a list of threads which I have paginated to use an endless scroll the issue I'm having (well my users) is an OutOfMemoryError: Failed to allocate a [x] byte allocation with [y] free bytes and [z] until OOM. the x, y and z attribute is different per user but the cause of the bug is always in the same place and it's when I refresh the posts. I'm completely out of my depth here as I have no idea how to optimise my code or make it so this doesn't happen. As it's the biggest crash on my app at the moment. I've posted my PostFragment below please see the refreshPosts(ArrayList<Posts> newObjects) method as this is where the crash is happening.
public class PostFragment extends Fragment implements View.OnClickListener {
private View mRootView;
private GridLayoutManager mLayoutManager;
private ThreadItem mThreads;
private PostItem mPost;
private PostAdapter mAdapter;
private PostResponse mData;
private EmoticonResponse mEmoticon;
private PostFeedDataFactory mDataFactory;
private EmoticonFeedDataFactory mEmoticonDataFactory;
private static PostFragment mCurrentFragment;
private int REQUEST_CODE;
//Flip
private boolean isFlipped = false;
private Animation flipAnimation;
#BindView(R.id.postsRecyclerView)
RecyclerView mRecyclerView;
#BindView(R.id.toolbarForPosts)
Toolbar mToolbar;
#BindView(R.id.threadText)
TextView mThreadText;
#BindView(R.id.flipText)
TextView mFlipTextView;
#BindView(R.id.shareText)
TextView mShareTextView;
#BindView(R.id.replyText)
TextView mReplyTextView;
#BindView(R.id.scrimColorView)
View mBackgroundView;
#BindView(R.id.fabMenu)
FloatingActionButton mFabMenu;
#BindView(R.id.flipFab)
FloatingActionButton mFlipFab;
#BindView(R.id.shareFab)
FloatingActionButton mShareFab;
#BindView(R.id.replyFab)
FloatingActionButton mReplyFab;
//Collapsing Toolbar
#BindView(R.id.postParentAppBarLayout)
AppBarLayout postAppBarLayout;
#BindView(R.id.postCollapseToolbar)
CollapsingToolbarLayout postCollapseToolbarLayout;
#BindView(R.id.mainImageContainer)
ViewGroup mainContainer;
//Back to top
#BindView(R.id.backToTopButton)
Button mBackToTop;
public static boolean isFromReply;
//FAB
private boolean mIsFabOpen = false;
private Animation fab_open, fab_close, rotate_forward, rotate_backward;
//Pagination
private int mCurrentPage = 1;
private ArrayList<Posts> postList = new ArrayList<>();
private boolean mIsLoading = false;
private boolean mIsLastPage = false;
public static PostFragment newInstance(#NonNull ThreadItem threadItem) {
Bundle args = new Bundle();
args.putParcelable("ThreadItem", Parcels.wrap(threadItem));
mCurrentFragment = new PostFragment();
mCurrentFragment.setArguments(args);
isFromReply = false;
return mCurrentFragment;
}
public static PostFragment newPostInstance(#NonNull PostItem postItem) {
Bundle args = new Bundle();
args.putParcelable("PostItemFromCompose", Parcels.wrap(postItem));
mCurrentFragment = new PostFragment();
mCurrentFragment.setArguments(args);
isFromReply = true;
return mCurrentFragment;
}
public PostFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mRootView = inflater.inflate(R.layout.fragment_post, container, false);
if (savedInstanceState == null) {
ButterKnife.bind(this, mRootView);
initUI();
}
return mRootView;
}
private void initUI() {
//UI Setup
mLayoutManager = new GridLayoutManager(getActivity(), 1);
mRecyclerView.setLayoutManager(mLayoutManager);
mDataFactory = new PostFeedDataFactory(getActivity());
mEmoticonDataFactory = new EmoticonFeedDataFactory(getActivity());
TextView textThreadTopic = (TextView) mRootView.findViewById(R.id.threadTopic);
TextView textNumPosts = (TextView) mRootView.findViewById(R.id.numPosts);
//FAB onClick Set-Up
mFabMenu.setOnClickListener(this);
mShareFab.setOnClickListener(this);
mReplyFab.setOnClickListener(this);
mFlipFab.setOnClickListener(this);
//FAB Animation Set up
fab_open = AnimationUtils.loadAnimation(getActivity().getApplicationContext(),
R.anim.fab_open);
fab_close = AnimationUtils.loadAnimation(getActivity().getApplicationContext(),
R.anim.fab_close);
rotate_forward = AnimationUtils.loadAnimation(getActivity().getApplicationContext(),
R.anim.rotate_forward);
rotate_backward = AnimationUtils.loadAnimation(getActivity().getApplicationContext(),
R.anim.rotate_backward);
//Toolbar
((AppCompatActivity) getActivity()).setSupportActionBar(mToolbar);
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayShowTitleEnabled(false);
mToolbar.setNavigationIcon(R.drawable.ic_back_white);
mToolbar.invalidate();
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getActivity().finish();
}
});
//Load Parcel
Intent intent = getActivity().getIntent();
mThreads = Parcels.unwrap(getArguments().getParcelable("ThreadItem"));
mPost = Parcels.unwrap(getArguments().getParcelable("PostItemFromCompose"));
if (mThreads != null) {
if (mThreads.getName() != null) {
mThreadText.setText(mThreads.getName());
}
if (mThreads.getTopic_name() != null) {
textThreadTopic.setText(mThreads.getTopic_name());
}
if (mThreads.getNum_posts() != null) {
int numPosts = Integer.parseInt(mThreads.getNum_posts());
if (numPosts > 1000) {
textNumPosts.setText("1K");
} else {
textNumPosts.setText(mThreads.getNum_posts());
}
}
}
postAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
boolean isShow = false;
int scrollRange = -1;
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if (scrollRange == -1) {
scrollRange = appBarLayout.getTotalScrollRange();
}
if (scrollRange + verticalOffset == 0) {
postCollapseToolbarLayout.setTitle("Threads");
mainContainer.setVisibility(View.INVISIBLE);
isShow = true;
} else if (isShow) {
postCollapseToolbarLayout.setTitle("");
isShow = false;
mainContainer.setVisibility(View.VISIBLE);
}
}
});
flipAnimation =
AnimationUtils.loadAnimation(getActivity().getApplicationContext(), R.anim.flip);
loadData(true, 1);
}
private void loadData(final boolean firstLoad, int readDirection) {
if (isFromReply) {
if (mPost.getThread_id() != null) {
mDataFactory.getPostFeed(mPost.getThread_id(), readDirection, mCurrentPage,
new PostFeedDataFactory.PostFeedDataFactoryCallback() {
#Override
public void onPostDataReceived(PostResponse response) {
mData = response;
if (mData.getItems() != null) {
for (int i = 0; i < mData.getItems().size(); i++) {
Posts singlePost = response.getItems().get(i);
postList.add(singlePost);
}
if (firstLoad) {
mIsLoading = false;
mData.getItems().clear();
mData.getItems().addAll(postList);
mEmoticonDataFactory.getEmoticonFeed(
new EmoticonFeedDataFactory.EmoticonFeedDataFactoryCallback() {
#Override
public void onEmoticonDataReceived(EmoticonResponse response) {
mEmoticon = response;
populateUIWithData();
}
#Override
public void onEmoticonDataFailed(Exception exception) {
}
});
} else {
mIsLoading = false;
refreshPosts(postList);
}
if (mData.getItems().size() > 0) {
if (Integer.valueOf(mData.getTotalPosts()) >= response.getItems().size()) {
mCurrentPage++;
} else {
mIsLastPage = true;
}
}
}
}
#Override
public void onPostDataFailed(Exception exception) {
customToast("Error: " + exception.toString());
}
});
}
} else {
if (mThreads.getId() != null)
mDataFactory.getPostFeed(mThreads.getId(), readDirection, mCurrentPage,
new PostFeedDataFactory.PostFeedDataFactoryCallback() {
#Override
public void onPostDataReceived(PostResponse response) {
mData = response;
if (mData.getItems() != null) {
for (int i = 0; i < mData.getItems().size(); i++) {
Posts singlePost = response.getItems().get(i);
postList.add(singlePost);
}
if (firstLoad) {
mIsLoading = false;
mData.getItems().clear();
mData.getItems().addAll(postList);
mEmoticonDataFactory.getEmoticonFeed(
new EmoticonFeedDataFactory.EmoticonFeedDataFactoryCallback() {
#Override
public void onEmoticonDataReceived(EmoticonResponse response) {
mEmoticon = response;
populateUIWithData();
}
#Override
public void onEmoticonDataFailed(Exception exception) {
}
});
} else {
mIsLoading = false;
refreshPosts(postList);
}
if (mData.getItems().size() > 0) {
if (Integer.valueOf(mData.getTotalPosts()) >= response.getItems().size()) {
mCurrentPage++;
} else {
mIsLastPage = true;
}
}
}
}
#Override
public void onPostDataFailed(Exception exception) {
customToast("Error: " + exception.toString());
}
});
}
}
private void populateUIWithData() {
ImageButton moreOptionsButton = (ImageButton) mRootView.findViewById(R.id.moreOptions);
moreOptionsButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
PopupMenu popupMenu = new PopupMenu(v.getContext(), v);
popupMenu.inflate(R.menu.thread_options);
popupMenu.getMenu();
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
#Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.watch:
WatchedThreadsRequestData watchedThreadsRequestData = new WatchedThreadsRequestData(getActivity());
watchedThreadsRequestData.setWatchedThread(mThreads.getId(), new WatchedThreadsRequestData.WatchedThreadsFeedback() {
#Override
public void onWatchedRequestReceived(ThreadResponse response) {
customToast("Thread watched");
}
#Override
public void onWatchedRequestFailed(Exception exception) {
customToast("Thread wasn't watched: " + exception.toString());
}
});
return true;
case R.id.shareThread:
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.putExtra(Intent.EXTRA_TEXT, mThreads.getName() + " - " + Constants.LIVE_URL +
"talk/" + mThreads.getTopic_url() + '/' + mThreads.getThread_url());
sharingIntent.setType("text/plain");
getActivity().startActivity(Intent.createChooser(sharingIntent, "Share via"));
return true;
case R.id.hideThread:
customToast("Hide: coming soon");
return true;
default:
customToast("Somethings Wrong");
return true;
}
}
});
setForceShowIcon(popupMenu);
popupMenu.show();
}
});
if (mAdapter == null) {
mAdapter = new PostAdapter(getActivity(), mData, mEmoticon);
mRecyclerView.setAdapter(mAdapter);
} else {
mAdapter.setData(mData.getItems());
mAdapter.notifyDataSetChanged();
}
mRecyclerView.addOnScrollListener(paginationListener);
}
public static void setForceShowIcon(PopupMenu popupMenu) {
try {
Field[] fields = popupMenu.getClass().getDeclaredFields();
for (Field field : fields) {
if ("mPopup".equals(field.getName())) {
field.setAccessible(true);
Object menuPopupHelper = field.get(popupMenu);
Class<?> classPopupHelper = Class.forName(menuPopupHelper
.getClass().getName());
Method setForceIcons = classPopupHelper.getMethod(
"setForceShowIcon", boolean.class);
setForceIcons.invoke(menuPopupHelper, true);
break;
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
private RecyclerView.OnScrollListener paginationListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
boolean hasEnded = newState == SCROLL_STATE_IDLE;
if (hasEnded) {
mFabMenu.show();
mFabMenu.setClickable(true);
} else {
if (mIsFabOpen)
closeMenu();
mFabMenu.hide();
mFabMenu.setClickable(false);
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = mLayoutManager.getChildCount();
int totalItemCount = mLayoutManager.getItemCount();
int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
if (!mIsLoading && !mIsLastPage) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount) {
loadMoreItems();
}
}
//Back to top
if (mLayoutManager.findLastVisibleItemPosition() == totalItemCount - 1) {
mBackToTop.setVisibility(View.VISIBLE);
mBackToTop.setClickable(true);
mBackToTop.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mLayoutManager.scrollToPositionWithOffset(0,0);
}
});
} else {
mBackToTop.setVisibility(View.GONE);
mBackToTop.setClickable(false);
}
}
};
private void loadMoreItems() {
if (!isFlipped) {
mIsLoading = true;
loadData(false, 1);
} else {
mIsLoading = true;
loadData(false, -1);
}
}
private void refreshPosts(ArrayList<Posts> newObjects) {
postList.addAll(newObjects);
populateUIWithData();
}
#Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.fabMenu:
animateFAB();
break;
case R.id.shareFab:
share();
break;
case R.id.replyFab:
reply();
break;
case R.id.flipFab:
flip();
break;
}
}
public void animateFAB() {
if (mIsFabOpen) {
closeMenu();
} else {
mFabMenu.startAnimation(rotate_forward);
mReplyFab.startAnimation(fab_open);
mShareFab.startAnimation(fab_open);
mFlipFab.startAnimation(fab_open);
mReplyFab.setClickable(true);
mShareFab.setClickable(true);
mFlipFab.setClickable(true);
mFlipTextView.setVisibility(View.VISIBLE);
mShareTextView.setVisibility(View.VISIBLE);
mReplyTextView.setVisibility(View.VISIBLE);
mBackgroundView.setVisibility(View.VISIBLE);
mIsFabOpen = true;
}
}
private void closeMenu() {
mFabMenu.startAnimation(rotate_backward);
mReplyFab.startAnimation(fab_close);
mShareFab.startAnimation(fab_close);
mFlipFab.startAnimation(fab_close);
mReplyFab.setClickable(false);
mShareFab.setClickable(false);
mFlipFab.setClickable(false);
mFlipTextView.setVisibility(View.INVISIBLE);
mShareTextView.setVisibility(View.INVISIBLE);
mReplyTextView.setVisibility(View.INVISIBLE);
mBackgroundView.setVisibility(View.INVISIBLE);
mIsFabOpen = false;
}
private void reply() {
PreferenceConnector.writeString(getActivity().getApplicationContext(), "threadID", mThreads.getId());
PreferenceConnector.writeString(getActivity().getApplicationContext(), "threadTitle", mThreads.getName());
if (PreferenceConnector.readString(getActivity(), "authToken") == null ||
PreferenceConnector.readString(getActivity(), "authToken").equalsIgnoreCase("skip")) {
final AlertDialog.Builder loginDialog = new AlertDialog.Builder(getActivity());
loginDialog.setTitle("Please log in");
loginDialog.setMessage("You need to be logged in to reply");
loginDialog.setPositiveButton("Log in", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(getActivity().getApplicationContext(), LoginActivity.class);
startActivity(intent);
}
});
loginDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
loginDialog.show();
} else {
closeMenu();
Intent intent = new Intent(getActivity().getApplicationContext(), NewPostActivity.class);
intent.putExtra("Threads", Parcels.wrap(mThreads));
getActivity().finish();
startActivityForResult(intent, REQUEST_CODE);
}
}
private void share() {
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.putExtra(Intent.EXTRA_TEXT, mThreads.getName() + " - " + Constants.LIVE_URL +
"talk/" + mThreads.getTopic_url() + '/' + mThreads.getThread_url());
sharingIntent.setType("text/plain");
startActivity(Intent.createChooser(sharingIntent, "Share via"));
}
private void flip() {
if (!isFlipped) {
mAdapter.clearAll();
isFlipped = true;
mRecyclerView.startAnimation(flipAnimation);
loadData(false, -1);
closeMenu();
} else {
mAdapter.clearAll();
isFlipped = false;
mRecyclerView.startAnimation(flipAnimation);
loadData(true, 1);
closeMenu();
}
}
private void customToast(String toastMessage) {
LayoutInflater inflater = getActivity().getLayoutInflater();
View layout = inflater.inflate(R.layout.custom_toast,
(ViewGroup) getActivity().findViewById(R.id.toastContainer));
TextView customToastText = (TextView) layout.findViewById(R.id.customToastText);
customToastText.setText(toastMessage);
Toast toast = new Toast(getActivity().getApplicationContext());
toast.setGravity(Gravity.BOTTOM, 0, 25);
toast.setDuration(Toast.LENGTH_LONG);
toast.setView(layout);
toast.show();
}
#Override
public void onResume() {
super.onResume();
if (mData != null && mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
getView().setFocusableInTouchMode(true);
getView().requestFocus();
getView().setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
if (mIsFabOpen) {
closeMenu();
} else {
getActivity().finish();
}
return true;
}
return false;
}
});
}
public void updateView() {
mAdapter.notifyDataSetChanged();
}
}
Thanks in advance once again.
Your problem basically boils down to this:
private void refreshPosts(ArrayList<Posts> newObjects) {
postList.addAll(newObjects);
populateUIWithData();
}
The list can only get bigger, never smaller. If the server has lots and lots of posts, then OutOfMemory is pretty much inevitable.
One approach to solving this problem is to use an LRU (Least Recently Used) cache. There is a utility class you can use: android.util.LruCache.
An LRU Cache is essentially a Map. Items are stored with a key, like an ID. With an LRU cache, you put new items in, but once a pre-determined limit is reached, old items start getting pushed out to make room for new items.
This will save memory, but make a lot more management code for you.
Your adapter, instead of having a list of posts, will just have a list of the post IDs. This should be much easier on memory.
As the user scrolls and you collect more posts, you add the post ID to the list, and map the post into the LRU cache using the post ID.
When you bind to the list item view, you look up the post using the post's ID in the LRU cache.
If it's there, great. That's called a cache hit. Bind the post to the
list item view.
If not, then you have a cache miss. You have some work to do.
Start a server request to retrieve the post by ID. I see your current code just retrieves blocks of posts, so you'll need some new server code here.
When the request completes, put the post in the LRU cache and let the adapter know your item has changed using adapter.notifyItemChanged(). Unless the user has scrolled beyond it, the RecyclerView should try to bind with the list item view again. This time, you should get a cache hit.
This is the basic idea. I'd write some code, but I still have a lot of questions since I can't see your model classes, data factories, and adapter class.
Once you have it working, you have to tune the limit on the cache so that it's low enough not to overrun memory, but high enough that your hit/miss ratio isn't close to zero.
BTW, I noticed that you are making the mistake of creating a new adapter and handing it to the RecyclerView each time you get a block of posts. You should create your adapter once, keep a reference to it and update it. Have a method that adds a block of posts then calls notifyDataSetChanged().
Another idea for conserving memory is to use text compression. If the problem is more a result of a large average size of the post rather than a large number of posts, you might explore this idea in addition to the LRU cache.
The concept is that you could take posts over a certain size, write them into a buffer using a ZipOutputStream then save the buffer in memory. When it's time to display the post, you read the buffer with a ZipInputStream to uncompress the text. Here the issue is performance as the compression/decompression is pretty CPU-intensive. But if the problem is really long posts, this approach might be something to consider.
An even better approach: Only save the first part of the post as an "overview" display in the list. When the user clicks on the list item, retrieve the entire post from the server and display that in another page.
I'm trying to make a simple app that lists all the found Ibeacons in a ListView and changes the RSSI values according to the distance the user is from the beacons itself.
The app works fine, but the problem I'm having is that if a beacon is out of reach it does not get removed from the list. Any ideas on how to remove the item when the beacon isn't in range anymore?
I have the following code:
MainActivity.java:
public class MainActivity extends Activity implements BeaconConsumer {
public ListView list;
public BeaconAdapter adapter;
public ArrayList<Beacon> arrayL = new ArrayList<>();
public LayoutInflater inflater;
public BeaconManager mBeaconManager;
public boolean beaconPresent;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
list = (ListView)findViewById(R.id.lijst);
mBeaconManager = BeaconManager.getInstanceForApplication(this.getApplicationContext());
mBeaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout("s:0-1=feaa,m:2-2=00,p:3-3:-41,i:4-13,i:14-19"));
mBeaconManager.setForegroundBetweenScanPeriod(100);
mBeaconManager.bind(this);
adapter = new BeaconAdapter();
list.setAdapter(adapter);
inflater =(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void onBeaconServiceConnect() {
Region region = new Region("all-beacons-region", null, null, null);
try {
mBeaconManager.startRangingBeaconsInRegion(region);
} catch (RemoteException e) {
e.printStackTrace();
}
mBeaconManager.setRangeNotifier(new RangeNotifier() {
#Override
public void didRangeBeaconsInRegion(final Collection<Beacon> beacons, Region region) {
runOnUiThread(new Runnable() {
#Override
public void run() {
ArrayList<Beacon> allRangedBeacons = (ArrayList<Beacon>) beacons;
ArrayList<Beacon> newRangedBeacons = new ArrayList<>();
ArrayList<Beacon> cloneArraylistIBeacon = (ArrayList<Beacon>) arrayL.clone();
ArrayList<Beacon>nonRangedBeacons = new ArrayList<>();
int index = 0;
for (Beacon presentBeacons : cloneArraylistIBeacon) {
beaconPresent = false;
for (Beacon eachRangedBeacon : allRangedBeacons) {
if (presentBeacons.equals(eachRangedBeacon)) {
arrayL.remove(index);
arrayL.add(index, eachRangedBeacon);
beaconPresent = true;
}
if(beaconPresent = false) {
nonRangedBeacons.add(presentBeacons);
}
}
index++;
}
for (Beacon eachRangedBeacon : allRangedBeacons) {
beaconPresent = false;
for (Beacon presentBeacons : cloneArraylistIBeacon) {
if (eachRangedBeacon.equals(presentBeacons)) {
beaconPresent = true;
}
}
if (!beaconPresent) {
newRangedBeacons.add(eachRangedBeacon);
}
}
arrayL.remove(nonRangedBeacons);
arrayL.addAll(newRangedBeacons);
adapter.notifyDataSetChanged();
}
});
}
});
}
protected void onPause() {
super.onPause();
mBeaconManager.unbind(this);
}
private class BeaconAdapter extends BaseAdapter {
#Override
public int getCount() {
if (arrayL != null && arrayL.size() > 0) {
return arrayL.size();
} else {
return 0;
}
}
#Override
public Beacon getItem(int position) {
return arrayL.get(position);
}
#Override
public long getItemId(int arg0) {
return arg0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
holder = new ViewHolder(convertView = inflater.inflate(R.layout.tupple_monitoring, null));
try {
holder.uuid.setText("UUID: " + arrayL.get(position).getId2());
holder.rssi.setText("RSSI: " + arrayL.get(position).getRssi());
holder.txpow.setText("TXPOW: " + arrayL.get(position).getTxPower());
return convertView;
}catch(Exception e) {
e.printStackTrace();
}
return convertView;
}
}
private class ViewHolder {
private TextView uuid;
private TextView rssi;
private TextView txpow;
public ViewHolder(View view) {
uuid = (TextView)view.findViewById(R.id.BEACON_uuid);
rssi = (TextView)view.findViewById(R.id.BEACON_rssi);
txpow = (TextView)view.findViewById(R.id.BEACON_txpower);
view.setTag(this);
}
}
}
If you only want to display beacons in range, every time you receive a list of beacons simply change the adapter source list.
arrayL.clear();
arrayL.addAll(beacons);
adapter.notifyDataSetChanged();
To avoid jumping around if list items, maybe sort the beacons by their RSSI before displaying them.
Because the Android Beacon Library already tracks the list of visible beacons and updates it in the ranging callback, you can simply refresh the whole list in your BeaconAdapter each time. Like this:
#Override
public void didRangeBeaconsInRegion(final Collection<Beacon> beacons, Region region) {
runOnUiThread(new Runnable() {
#Override
public void run() {
arrayL = new ArrayList<Beacon>(beacons);
adapter.notifyDataSetChanged();
}
});
}