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.
Related
I am trying to update the UI depending on whether the data is being loaded or has loaded but it is not working properly. I am using enum class for different states.
Initially the error was
Attempt to invoke virtual method 'void androidx.lifecycle.LiveData.observe(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Observer)' on a null object reference
Then I passed an empty new MutableLiveData()<>. Now, it doesn't crashes the application, however, the getDataStatus() observer isn't working correctly. Kindly look at my implementations and see if they are right.
DataSource
public class ArticlesDataSource extends PageKeyedDataSource<Integer, NewsItem> {
private static final int FIRST_PAGE = 1;
private static final String TAG = "ArticlesDataSource";
public static final String SORT_ORDER = "publishedAt";
public static final String LANGUAGE = "en";
public static final String API_KEY = Utils.API_KEY;
public static final int PAGE_SIZE = 10;
private String mKeyword;
private MutableLiveData<DataStatus> dataStatusMutableLiveData = new MutableLiveData<>();
public ArticlesDataSource(String keyword) {
mKeyword = keyword;
dataStatusMutableLiveData = new MutableLiveData<>();
}
public MutableLiveData<DataStatus> getDataStatusMutableLiveData() {
return dataStatusMutableLiveData;
}
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull LoadInitialCallback<Integer, NewsItem> callback) {
dataStatusMutableLiveData.postValue(DataStatus.LOADING);
NewsAPI newsAPI = ServiceGenerator.createService(NewsAPI.class);
Call<RootJsonData> call = newsAPI.searchArticlesByKeyWord(mKeyword, SORT_ORDER, LANGUAGE, API_KEY, FIRST_PAGE, PAGE_SIZE);
call.enqueue(new Callback<RootJsonData>() {
#Override
public void onResponse(Call<RootJsonData> call, Response<RootJsonData> response) {
if (response.body() != null) {
callback.onResult(response.body().getNewsItems(), null, FIRST_PAGE + 1);
dataStatusMutableLiveData.postValue(DataStatus.LOADED);
}
}
#Override
public void onFailure(Call<RootJsonData> call, Throwable t) {
Log.d(TAG, "onFailure: " + t.getMessage());
dataStatusMutableLiveData.postValue(DataStatus.ERROR);
}
});
}
#Override
public void loadBefore(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<Integer, NewsItem> callback) {
NewsAPI newsAPI = ServiceGenerator.createService(NewsAPI.class);
Call<RootJsonData> call = newsAPI.searchArticlesByKeyWord(mKeyword, SORT_ORDER, LANGUAGE, API_KEY, FIRST_PAGE, PAGE_SIZE);
call.enqueue(new Callback<RootJsonData>() {
#Override
public void onResponse(Call<RootJsonData> call, Response<RootJsonData> response) {
// if the current page is greater than one
// we are decrementing the page number
// else there is no previous page
Integer adjacentKey = (params.key > 1) ? params.key - 1 : null;
if (response.body() != null) {
// passing the loaded data
// and the previous page key
callback.onResult(response.body().getNewsItems(), adjacentKey);
}
}
#Override
public void onFailure(Call<RootJsonData> call, Throwable t) {
Log.d(TAG, "onFailure: " + t.getMessage());
}
});
}
#Override
public void loadAfter(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<Integer, NewsItem> callback) {
NewsAPI newsAPI = ServiceGenerator.createService(NewsAPI.class);
Call<RootJsonData> call = newsAPI.searchArticlesByKeyWord(mKeyword, SORT_ORDER, LANGUAGE, API_KEY, params.key, PAGE_SIZE);
call.enqueue(new Callback<RootJsonData>() {
#Override
public void onResponse(Call<RootJsonData> call, Response<RootJsonData> response) {
dataStatusMutableLiveData.postValue(DataStatus.LOADED);
if (response.code() == 429) {
// no more results
List<NewsItem> emptyList = new ArrayList<>();
callback.onResult(emptyList, null);
}
if (response.body() != null) {
// if the response has next page
// incrementing the next page number
Integer key = params.key + 1;
// passing the loaded data and next page value
if (!response.body().getNewsItems().isEmpty()) {
callback.onResult(response.body().getNewsItems(), key);
}
}
}
#Override
public void onFailure(Call<RootJsonData> call, Throwable t) {
Log.d(TAG, "onFailure: " + t.getMessage());
dataStatusMutableLiveData.postValue(DataStatus.ERROR);
}
});
}
}
DataSourceFactory
public class ArticlesDataSourceFactory extends DataSource.Factory {
private final MutableLiveData<ArticlesDataSource> itemLiveDataSource;
private String mQuery;
private final LiveData<DataStatus> dataStatusLiveData = Transformations.switchMap(itemLiveDataSource, (itemDataSource) -> {
return itemDataSource.getDataStatusMutableLiveData();
});
public ArticlesDataSourceFactory() {
mQuery = "news";
itemLiveDataSource = new MutableLiveData<>();
}
#Override
public DataSource<Integer, NewsItem> create() {
ArticlesDataSource itemDataSource = new ArticlesDataSource(mQuery);
itemLiveDataSource.postValue(itemDataSource);
// dataStatusMutableLiveData = itemDataSource.getDataStatusMutableLiveData();
return itemDataSource;
}
public MutableLiveData<ArticlesDataSource> getArticlesLiveDataSource() {
return itemLiveDataSource;
}
public void setQuery(String query) {
mQuery = query;
}
public MutableLiveData<DataStatus> getDataStatusMutableLiveData() {
return dataStatusMutableLiveData;
}
public void setDataStatusMutableLiveData(DataStatus dataStatus){
dataStatusMutableLiveData.postValue(dataStatus);
}
public LiveData<DataStatus> getDataStatusLiveData() {
return dataStatusLiveData;
}
}
ViewModel
public class ArticlesViewModel extends ViewModel {
public LiveData<PagedList<NewsItem>> itemPagedList;
private MutableLiveData<ArticlesDataSource> liveDataSource;
private ArticlesDataSourceFactory articlesDataSourceFactory;
private LiveData dataStatus = new MutableLiveData<>();
public ArticlesViewModel() {
articlesDataSourceFactory = new ArticlesDataSourceFactory();
liveDataSource = articlesDataSourceFactory.getArticlesLiveDataSource();
dataStatus = articlesDataSourceFactory.getDataStatusMutableLiveData();
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder())
.setEnablePlaceholders(false)
.setPageSize(10).build();
itemPagedList = (new LivePagedListBuilder(articlesDataSourceFactory, pagedListConfig)).build();
}
public void setKeyword(String query) {
if (query.equals("") || query.length() == 0)
articlesDataSourceFactory.setDataStatusMutableLiveData(DataStatus.EMPTY);
else {
articlesDataSourceFactory.setQuery(query);
refreshData();
}
}
void refreshData() {
if (itemPagedList.getValue() != null) {
itemPagedList.getValue().getDataSource().invalidate();
}
}
public LiveData<DataStatus> getDataStatus() {
return dataStatus;
}
}
Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_articles, container, false);
mContext = getActivity();
progressBar = rootView.findViewById(R.id.progress_circular);
emptyStateTextView = rootView.findViewById(R.id.empty_view);
swipeRefreshLayout = rootView.findViewById(R.id.swipe_refresh);
textViewTitle = rootView.findViewById(R.id.text_view_top_headlines);
recyclerView = rootView.findViewById(R.id.recycler_view);
if (savedInstanceState != null) {
keyword = savedInstanceState.getString("keyword");
}
initEmptyRecyclerView();
articlesViewModel = ViewModelProviders.of(this).get(ArticlesViewModel.class);
articlesViewModel.itemPagedList.observe(getViewLifecycleOwner(), new Observer<PagedList<NewsItem>>() {
#Override
public void onChanged(PagedList<NewsItem> newsItems) {
adapter.submitList(newsItems);
// TODO: Handle UI changes
// handleUIChanges(newsItems);
}
});
articlesViewModel.getDataStatus().observe(getViewLifecycleOwner(), new Observer<DataStatus>() {
#Override
public void onChanged(DataStatus dataStatus) {
switch (dataStatus) {
case LOADED:
progressBar.setVisibility(View.GONE);
emptyStateTextView.setVisibility(View.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
textViewTitle.setVisibility(View.VISIBLE);
break;
case LOADING:
progressBar.setVisibility(View.VISIBLE);
swipeRefreshLayout.setRefreshing(true);
textViewTitle.setVisibility(View.INVISIBLE);
emptyStateTextView.setVisibility(View.INVISIBLE);
break;
case EMPTY:
progressBar.setVisibility(View.GONE);
swipeRefreshLayout.setRefreshing(false);
textViewTitle.setVisibility(View.INVISIBLE);
emptyStateTextView.setVisibility(View.VISIBLE);
emptyStateTextView.setText(R.string.no_news_found);
break;
case ERROR:
progressBar.setVisibility(View.GONE);
swipeRefreshLayout.setRefreshing(false);
textViewTitle.setVisibility(View.INVISIBLE);
emptyStateTextView.setVisibility(View.VISIBLE);
emptyStateTextView.setText(R.string.no_internet_connection);
break;
}
}
});
swipeRefreshLayout.setOnRefreshListener(() -> {
articlesViewModel.setKeyword(keyword);
});
setHasOptionsMenu(true);
return rootView;
}
DataStatus
public enum DataStatus {
ERROR,
LOADING,
LOADED,
EMPTY
}
When you call invalidate(), a new datasource will be created by the factory. However, you are directly exposing the data status liveData of the "current" created datasource, without taking into consideration that more will be created in the future.
The solution is to store the current data source in the factory in a MutableLiveData, and expose the "most recent current data status" using switchMap.
public class ArticlesDataSourceFactory extends DataSource.Factory {
private final MutableLiveData<ArticlesDataSource> itemLiveDataSource = new MutableLiveData<>();
private String mQuery = "news";
private final LiveData<DataStatus> dataStatusLiveData = Transformations.switchMap(itemLiveDataSource, (itemDataSource) -> {
return itemDataSource.getDataStatusMutableLiveData();
});
public ArticlesDataSourceFactory() {
}
#Override
public DataSource<Integer, NewsItem> create() {
ArticlesDataSource itemDataSource = new ArticlesDataSource(mQuery);
itemLiveDataSource.postValue(itemDataSource);
...
public LiveData<DataStatus> getDataStatusLiveData() {
return dataStatusLiveData;
}
i am scanning devices from a bluetooth and getting their data from their advertisement,so one of the device gives the data of other devices which is not in ranged from the mobile device,so i specifically filter the data of that device which i wanted to show in recycler view in automatically updating the rssi values.
let's say,
a device A which gives the data of 3 devices B,C,D containing their rssi values continuously,so what i want to show the list of device B,C,D with their rssi values which is changing continuously.
What i have done until now is scan that device "A",and extract the data of B and shown in recyclerview,but when another device i.e C's data comes in the old device i.e B got vanished and so on.
what i want is continuously changing data of B,C,D.
Adapter Class:-
public class ReapeaterDeviceAdapter extends RecyclerView.Adapter<ReapeaterDeviceAdapter.CryptoViewHolder> {
private ArrayList<RepeaterModel> data = new ArrayList<>();;
private Context mCtx;
public class CryptoViewHolder extends RecyclerView.ViewHolder {
private TextView mName, mPrice;
public CryptoViewHolder(View itemView) {
super(itemView);
mName = itemView.findViewById(R.id.txtName);
mPrice = itemView.findViewById(R.id.txtPrice);
}
}
public ReapeaterDeviceAdapter(Context ctx,ArrayList<RepeaterModel> data) {
this.mCtx = ctx;
this.data = data;
}
#Override
public CryptoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.repeater_dev_data,parent, false);
return new CryptoViewHolder(itemView);
}
#Override
public void onBindViewHolder(CryptoViewHolder holder, int position) {
System.out.println("onBinder");
holder.mName.setText(data.get(position).macdev);
holder.mPrice.setText(String.valueOf(data.get(position).rssi));
}
#Override
public void onBindViewHolder(CryptoViewHolder holder, int position, List<Object> payloads) {
if (payloads.isEmpty()) {
//System.out.println("onbinder");
super.onBindViewHolder(holder, position, payloads);
} else {
Bundle o = (Bundle) payloads.get(0);
//System.out.println("in keyset");
for (String key : o.keySet()) {
if (key.equals("price")) {
holder.mName.setText(data.get(position).macdev);
holder.mPrice.setText(String.valueOf(data.get(position).rssi));
//holder.mPrice.setTextColor(Color.GREEN);
//this.notifyItemChanged(position);
}
}
}
}
#Override
public int getItemCount() {
return data.size();
}
public ArrayList<RepeaterModel> getData() {
return data;
}
public void setData(ArrayList<RepeaterModel> newData) {
MyDiffUtilCallBack diffCallBack = new MyDiffUtilCallBack(newData, this.data);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallBack);
//this.setData(newData);
// this.data=newData;
this.data.clear();
// this.data = newData;
this.data.addAll(newData);
diffResult.dispatchUpdatesTo(this);
//this.notifyItemRangeChanged(0, this.getItemCount());
//this.notifyDataSetChanged();
//System.out.println("Here");
}
}
Model Class:-
public String macdev;
public int rssi ;
public int imageid;
public RepeaterModel(String macdev, int rssi, int imageid) {
this.macdev = macdev;
this.rssi = rssi;
this.imageid = imageid;
}
public String getMacdev() {
return macdev;
}
public void setMacdev(String macdev) {
this.macdev = macdev;
}
public int getRssi() {
return rssi;
}
public void setRssi(int rssi) {
this.rssi = rssi;
}
public int getImageid() {
return imageid;
}
public void setImageid(int imageid) {
this.imageid = imageid;
}
#Override
public boolean equals(Object o) {
System.out.println("in equals");
if (this == o) return true;
if (!(o instanceof RepeaterModel)) return false;
RepeaterModel that = (RepeaterModel) o;
return getRssi() == that.getRssi() &&
getImageid() == that.getImageid() &&
getMacdev().equals(that.getMacdev());
}
#Override
public int hashCode() {
return Objects.hash(getMacdev(), getRssi(), getImageid());
}
/*#Override
public int hashCode() {
int result = Integer.valueOf(rssi);
// result = result + (imageid != 0 ? imageid.hashCode() : 0);
result = result + rssi.hashCode();
System.out.println("hash");
return result;
}*/
}
Diffutilcallback :-
ArrayList<RepeaterModel> newList;
ArrayList<RepeaterModel> oldList;
public MyDiffUtilCallBack(ArrayList<RepeaterModel> newList, ArrayList<RepeaterModel> oldList) {
this.newList = newList;
this.oldList = oldList;
}
#Override
public int getOldListSize() {
return oldList != null ? oldList.size() : 0;
}
#Override
public int getNewListSize() {
return newList != null ? newList.size() : 0;
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
if(newList.get(newItemPosition).getMacdev().equals(oldList.get(oldItemPosition).getMacdev()))
{
// Log.d("itemsame","in same");
return true;
}
else {
// Log.d("itemsame", "not same");
}
return false;
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition)
{
//System.out.println("in content same");
final RepeaterModel oldRepeater = oldList.get(oldItemPosition);
final RepeaterModel newRepeater = newList.get(newItemPosition);
if(oldRepeater.getRssi()!=(newRepeater.getRssi()))
{
//Log.d("item contenets","content different");
return false;
}
//Log.d("item contenets","content same");
return true;
}
#Nullable
#Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
RepeaterModel newModel = newList.get(newItemPosition);
RepeaterModel oldModel = oldList.get(oldItemPosition);
//System.out.println("getchange");
Bundle diff = new Bundle();
//if (newModel.getMacdev().equals(oldModel.getMacdev()))
//{
//System.out.println("getchange");
if (newModel.getRssi()!=(oldModel.getRssi())) {
diff.putInt("price", newModel.getRssi());
}
if (diff.size() == 0) {
return null;
}
// }
return diff;
//return super.getChangePayload(oldItemPosition, newItemPosition);
}
}```
Activity Class:-
public class RepeaterAdvertise extends AppCompatActivity {
private static BluetoothAdapter mBluetoothAdapter;
private static BluetoothLeScanner mLEScanner;
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;
private static boolean mScanning;
private static Handler mHandler;
private static final long SCAN_TIMEOUT = 20000;
public DevData data;
public Button cc;
//List<BluetoothDevice> mBluetoothDevice;
RecyclerView recyclerView;
ReapeaterDeviceAdapter reapeaterDeviceAdapter;
ArrayList<RepeaterModel> modelArrayList;// = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_repeater_advertise);
modelArrayList = new ArrayList<>();
recyclerView = (RecyclerView) findViewById(R.id.devdata);
//dummyData();
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
reapeaterDeviceAdapter = new ReapeaterDeviceAdapter(RepeaterAdvertise.this, modelArrayList);
recyclerView.setAdapter(reapeaterDeviceAdapter);
mHandler = new Handler();
final Intent intent = getIntent();
data = (DevData) intent.getSerializableExtra(SINGLE_DEV_DATA);
}
#Override
protected void onPause() {
super.onPause();
scanLeDevice(false,data.getMac_address());
}
#Override
protected void onResume() {
super.onResume();
scanLeDevice(true,data.getMac_address());
}
private void scanLeDevice(final boolean enable,String mac) {
if (enable) { // enable set to start scanning
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
if(mScanning) {
mScanning = false;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
//noinspection deprecation
mBluetoothAdapter.stopLeScan(mLeScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
invalidateOptionsMenu();
}
}
}, SCAN_TIMEOUT);
mScanning = true;
UUID[] motorServiceArray = {PSoCBleRobotService.getMotorServiceUUID()};
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
//noinspection deprecation
mBluetoothAdapter.startLeScan(motorServiceArray, mLeScanCallback);
} else { // New BLE scanning introduced in LOLLIPOP
ScanSettings settings;
List<ScanFilter> filters;
mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build();
filters = new ArrayList<>();
//ScanFilter filter = new ScanFilter.Builder().setServiceUuid(PUuid).build();
//ScanFilter filter = new ScanFilter.Builder().setManufacturerData(89,new byte[] {}).build();
ScanFilter filter = new ScanFilter.Builder().setDeviceAddress(mac).build();
filters.add(filter);
if(mLEScanner==null)
{
mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
}
mLEScanner.startScan(filters, settings, mScanCallback);
}
} else { // enable set to stop scanning
if(mScanning) {
mScanning = false;
if (Build.VERSION.SDK_INT < 21) {
//noinspection deprecation
mBluetoothAdapter.stopLeScan(mLeScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
}
}
invalidateOptionsMenu();
}
private final BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, int rssi, final byte[] scanRecord) {
runOnUiThread(new Runnable() {
#Override
public void run()
{
byte[] haha = scanRecord;
/* if(!mBluetoothDevice.contains(device))
{
//only add new devices
mBluetoothDevice.add(device);
mBleName.add(device.getName());
mBleArrayAdapter.notifyDataSetChanged(); // Update the list on the screen
}*/
}
});
}
};
public static String SbytesToHex(SparseArray<byte[]> bytes) {
StringBuilder builder = new StringBuilder();
byte[] dd = bytes.valueAt(0);
for (byte b: dd)
{
builder.append(String.format("%02x", b));
}
//System.out.println( dd.length);
return builder.toString();
}
public final ScanCallback mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
ScanRecord scanRecord = result.getScanRecord();
// mBluetoothDevice.add(result.getDevice());
SparseArray<byte[]> dataw = scanRecord.getManufacturerSpecificData();
if (dataw.size() > 0) {
//populate_devData(SbytesToHex(dataw));
String data = SbytesToHex(dataw);
ArrayList<RepeaterModel> repeaterModels= new ArrayList<>();
int rssi = Integer.valueOf(data.substring(12, 14), 16) - 256;
repeaterModels.add(new RepeaterModel(data.substring(0, 12), rssi, 1));
insert(repeaterModels);
} else if (false) {
//alertDialog.dismiss();
final Intent intent = new Intent(RepeaterAdvertise.this, ConfigurationView.class);
intent.putExtra(EXTRAS_BLE_ADDRESS, data.getMac_address());
intent.putExtra(EXTRAS_BLE_NAME, data.getName());
intent.putExtra(SINGLE_DEV_DATA, data.getDev_data());
scanLeDevice(false, data.getMac_address());
//mconfig=false;
startActivity(intent);
}
}
};
public void insert(ArrayList<RepeaterModel> rr)
{
modelArrayList.addAll(rr);
reapeaterDeviceAdapter.setData(rr);
}
}
add this line in insert function reapeaterDeviceAdapter.notifyDataSetChanged();
public void insert(ArrayList<RepeaterModel> rr){
modelArrayList.addAll(rr);
reapeaterDeviceAdapter.setData(rr);
reapeaterDeviceAdapter.notifyDataSetChanged();
}
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 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.
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();
}
});
}