Android - Alternative to Using Context Inside of Parcelable - android

I have a class I use for managing bitmaps in my application.
It needs to have a context passed to it to be able to use getResources().
Problem is I believe you can't use Context inside of a Parcelable class.
Anyone have any ideas for a solution?
Below is my code:
public class ImageManager implements Parcelable {
private static final long serialVersionUID = 66;
private HashMap<Integer, Bitmap> mBitmaps;
private HashMap<Integer, Drawable> mDrawables;
//private Context mContext;
private boolean mActive = true;
public ImageManager(Context c) {
mBitmaps = new HashMap<Integer, Bitmap>();
mDrawables = new HashMap<Integer, Drawable>();
//mContext = c;
}
public ImageManager(Parcel in) {
// TODO Auto-generated constructor stub
}
public Bitmap getBitmap(int resource) {
if (mActive) {
if (!mBitmaps.containsKey(resource)) {
mBitmaps.put(resource,
BitmapFactory.decodeResource(mContext.getResources(), resource));
}
return mBitmaps.get(resource);
}
return null;
}
public Drawable getDrawable(int resource) {
if (mActive) {
if (!mDrawables.containsKey(resource)) {
mDrawables.put(resource, mContext.getResources().getDrawable(resource));
}
return mDrawables.get(resource);
}
return null;
}
public void recycleBitmaps() {
Iterator itr = mBitmaps.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry e = (Map.Entry)itr.next();
((Bitmap) e.getValue()).recycle();
}
mBitmaps.clear();
}
public ImageManager setActive(boolean b) {
mActive = b;
return this;
}
public boolean isActive() {
return mActive;
}
#Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
// TODO Auto-generated method stub
dest.writeValue(mBitmaps);
dest.writeValue(mDrawables);
//dest.writeValue(mContext);
dest.writeByte((byte) (mActive ? 0x01 : 0x00));
}
public static final Parcelable.Creator<ImageManager> CREATOR = new Parcelable.Creator<ImageManager>() {
public ImageManager createFromParcel(Parcel in) {
return new ImageManager(in);
}
public ImageManager[] newArray(int size) {
return new ImageManager[size];
}
};
}

A "manager" normally can't be Parcelable or Serializable.
Parcelable or Serializable objects are kind of objects that just hold some data and don't perform any operations on it and don't need to reference Context.
When you pass objects as a Parcelable, the object received will be recreated so this way you can't preserve the same instance.
To solve your task, make ImageManager a singleton.
public final class ImageManager {
public static synchronized ImageManager getInstance(final Context context) {
if (sInstance == null) {
sInstance = new ImageManager(context.getApplicationContext());
}
return sInstance;
}
private static ImageManager sInstance;
private final Context mContext;
private ImageManager(Context c) {
mBitmaps = new HashMap<Integer, Bitmap>();
mDrawables = new HashMap<Integer, Drawable>();
}
And whenever you need it in Activity, call
ImageManager imageManager = ImageManager.getInstance(this);
This way the same ImageManager will be accessible in all Activities.

You can create your own Application implementation and add static getter.
public class Application extends android.app.Application {
private static Context context;
#Override
public void onCreate() {
super.onCreate();
context = this;
}
public static Context getContext(){
return context;
}
}
And use it everywhere you need:
Application.getContext();
But in general I thing it's not the best idea.

I suggest turning your class into a helper class by making the methods you need static and then passing Context as a parameter as well, like so:
public Bitmap getBitmap(int resource, Context context) { ... }
Don't store that reference to Context in your helper class, if you do you will most likely be leaking it.

Related

How to pass ArrayList that contains Base64 String to another activity from recyclerview

I am working on a project where i need to create Image Preview Functionality.For that i have created a recyclerview in which i am passing ArrayList of bitmap and displaying it in recyclerview.Now i am converting that arraylist into base64 string array and want to pass that arraylist into new activity using parcelable.
But i am getting TransactionTooLarge Execption.
Is there another way to pass the array to another activity?
Here is my adapter
public class ImageListAdapter extends RecyclerView.Adapter<ImageListAdapter.ViewHolder> {
private ArrayList<UploadImageModel> mBitmapArray;
private Context context;
private UploadImageModel mUploadImageModel;
private ArrayList<Base64ArrayModel> mBase64ArrayList;
private Base64ArrayModel mBase64ArrayModel;
public ImageListAdapter(ArrayList<UploadImageModel> mBitmapArray, ArrayList<Base64ArrayModel> mBase64ArrayList, Context context) {
this.mBitmapArray = mBitmapArray; //Here i am getting arraylist that contains bitmaps
this.context = context;
}
#Override
public ImageListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(context).inflate(R.layout.image_set, null);
ViewHolder holder = new ViewHolder(itemView);
return holder;
}
#Override
public void onBindViewHolder(ImageListAdapter.ViewHolder holder, int position) {
mUploadImageModel = mBitmapArray.get(position);
holder.UploadImageView.setImageBitmap(mUploadImageModel.getUploadImageBitmap());
holder.UploadImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent openPreviewActivity = new Intent(context, PreviewActivity.class);
openPreviewActivity.putParcelableArrayListExtra("myImageList",encodeList());
context.startActivity(openPreviewActivity);
}
});
}
#Override
public int getItemCount() {
return mBitmapArray.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView UploadImageView;
public ViewHolder(View itemView) {
super(itemView);
UploadImageView = (ImageView) itemView.findViewById(R.id.UploadImageView);
}
}
private ArrayList<Base64ArrayModel> encodeList() {
mBase64ArrayList = new ArrayList<>();
for (int i = 0; i < mBitmapArray.size(); i++) {
mBase64ArrayList.add(new Base64ArrayModel(ConstantFunction.encodeToBase64(mBitmapArray.get(i).getUploadImageBitmap(), Bitmap.CompressFormat.JPEG, 100)));
}
return mBase64ArrayList;
}
}
and the model i am using is as follows
public class Base64ArrayModel implements Parcelable {
public String mBase64BitmapString;
public String getmBase64BitmapString() {
return mBase64BitmapString;
}
public void setmBase64BitmapString(String mBase64BitmapString) {
this.mBase64BitmapString = mBase64BitmapString;
}
public Base64ArrayModel(String mBase64BitmapString)
{
this.mBase64BitmapString=mBase64BitmapString;
}
protected Base64ArrayModel(Parcel in) {
mBase64BitmapString = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mBase64BitmapString);
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<Base64ArrayModel> CREATOR = new Parcelable.Creator<Base64ArrayModel>() {
#Override
public Base64ArrayModel createFromParcel(Parcel in) {
return new Base64ArrayModel(in);
}
#Override
public Base64ArrayModel[] newArray(int size) {
return new Base64ArrayModel[size];
}
};
}
How can i pass that arrayList to new activity?
From the doc,
During a remote procedure call, the arguments and the return value of
the call are transferred as Parcel objects stored in the Binder
transaction buffer. If the arguments or the return value are too large
to fit in the transaction buffer, then the call will fail and
TransactionTooLargeException will be thrown.
The Binder transaction buffer has a limited fixed size, currently 1Mb,
which is shared by all transactions in progress for the process.
Consequently this exception can be thrown when there are many
transactions in progress even when most of the individual transactions
are of moderate size.
So, this basically means, you're trying to pass data with a size greater than the Binder Transaction Buffer can contain. To overcome this, you've to reduce the size of the data(base64String size, for your case). I can see you've this
ConstantFunction.encodeToBase64(mBitmapArray.get(i).getUploadImageBitmap(), Bitmap.CompressFormat.JPEG, 100) method for encoding a bitmap to base64String where you've passed 100 as compression level. In your implementation, if you use bitmap.compress method to compress the bitmap then try to reduce the number. The less the number the less quality it would get after the compression hence, you'll get small sized base64String in the end.
first, you add this line into your manifest file.
android:largeHeap="true"
Because simultaneously at a time your transaction too large. So make one singleton class like. It is not preferred way I want to suggest use database but if you have not any other choice than this one is better for you.
public class DataTransactionModel {
private static volatile DataTransactionModel instance = null;
private ArrayList<Base64ArrayModel> list = null;
private DataTransactionModel() {
}
public static synchronized DataTransactionModel getInstance() {
if (instance == null) {
synchronized (DataTransactionModel.class) {
if (instance == null) {
instance = new DataTransactionModel();
}
}
}
return instance;
}
public Bitmap getList() {
return list;
}
public void setList(Bitmap bitmap) {
this.bitmap = list;
}
}
Set data into this singleton class and then after get list of images with the help of singleton class methods.
...
#Override
public void onBindViewHolder(ImageListAdapter.ViewHolder holder, int position) {
mUploadImageModel = mBitmapArray.get(position);
holder.UploadImageView.setImageBitmap(mUploadImageModel.getUploadImageBitmap());
holder.UploadImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DataTransactionModel model = DataTransactionModel.getInstance();
model.setList(encodeList());
Intent openPreviewActivity = new Intent(context, PreviewActivity.class);
context.startActivity(openPreviewActivity);
}
});
}
You can use EventBus :
Create event and then post the arraylist and receive wherever you want to..
for E.g
compile 'org.greenrobot:eventbus:3.0.0'
//create class
public class Base64Event {
public final List< Base64ArrayModel > base64Array;
public Base64Event(List< Base64ArrayModel > base64Array){
}
}
//Post
EventBus.getDefault().postSticky(new Base64Event(base64Array));
//Reciever in activity
#Subscribe(threadMode = ThreadMode.MAIN)
public void anyName(Base64Event event) {
event. base64Array //here is the data passed
}

Saving dynamically added LinearLayouts without using savedInstanceState?

I have a layout in which I have dynamically added custom views at a push of a button. These layouts extend LinearLayout and each carry their own unique Action objects.
The views will disappear, however, if onCreate is called again, when the user navigates away or rotates the screen. I want to keep these custom ActionHolder views there. To add to the problem, the ActionHolder objects contain sensitive information. The Action objects themselves store a live timer(that is supposed to keep on ticking even if the app is off), as well as other information.
According to an answer below, I have done the following, but to no avail. Here is what I have so far:
public class ActionHolder extends LinearLayout implements Serializable {
/**
*
*/
private static final long serialVersionUID = 2271402255369440088L;
private Action action;
private String timer;
public static final int ACTION_TITLE = 0, ACTION_TIMER = 1,
PAUSEANDPLAY_BTN = 2, FINISH_BTN = 3;
public ActionHolder(Context context) {
super(context);
}
public ActionHolder(Context context, AttributeSet attr) {
super(context, attr);
}
public ActionHolder(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
}
public void initiate(Action input) {
// int hashedID = input.getActionName().hashCode();
// if (hashedID < 0)
// hashedID *= -1;
// this.setId(hashedID);
this.setOrientation(LinearLayout.VERTICAL);
this.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
action = input;
LayoutInflater inflater = LayoutInflater.from(getContext());
View view = inflater.inflate(R.layout.action_holder_layout, this, true);
TextView actionTitle = (TextView) view
.findViewById(com.tonimiko.mochi_bean.R.id.action_holder_title);
actionTitle.setText(action.getActionName());
actionTitle.setId(ActionHolder.ACTION_TITLE);
TextView actionTimer = (TextView) view
.findViewById(R.id.action_holder_timer);
actionTimer.setId(ActionHolder.ACTION_TIMER);
Button pauseBtn = (Button) view
.findViewById(com.tonimiko.mochi_bean.R.id.pause_and_play_timer_btn);
pauseBtn.setId(ActionHolder.PAUSEANDPLAY_BTN);
Button finishBtn = (Button) view
.findViewById(com.tonimiko.mochi_bean.R.id.finish_activity_button);
finishBtn.setId(ActionHolder.FINISH_BTN);
action.setActivityStartTime();
}
public Action finishAction() {
action.setActivityStopTime();
return action;
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
public String toString() {
return "Action stored: " + action.getActionName();
}
#Override
public boolean equals(Object other) {
ActionHolder otherObj = (ActionHolder) other;
if (this.action.getActionName().toUpperCase()
.equals(otherObj.action.getActionName().toUpperCase()))
return true;
return false;
}
#Override
public int hashCode() {
return action.getActionName().hashCode();
}
#Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
Bundle data = new Bundle();
data.putString("Timer", timer);
data.putSerializable("Action", action);
Log.e("debug", "View onSaveInstanceState called!"); // TODO
Parcelable test = new ActionHolderSavedState(superState, data);
if(test==null)
Log.e("debug", "NULL PARCELABLE"); // TODO
return new ActionHolderSavedState(superState, data);
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
Log.e("debug", "View onRestore called!");
if (state instanceof ActionHolderSavedState) {
final ActionHolderSavedState savedState = (ActionHolderSavedState) state;
this.action = savedState.getAction();
this.timer = savedState.getTimer();
// this.initiate(action);
super.onRestoreInstanceState(savedState.getSuperState());
Log.e("debug", "View onRestoreInstanceState finished"); // TODO
}
}
static class ActionHolderSavedState extends BaseSavedState {
private Action storedAction;
private String storedTimer;
public ActionHolderSavedState(Parcelable superState, Bundle data) {
super(superState);
storedTimer = data.getString("Timer");
storedAction = (Action) data.getSerializable("Action");
}
private ActionHolderSavedState(Parcel in) {
super(in);
storedTimer = in.readString();
storedAction = in.readParcelable(ActionHolder.class.getClassLoader());
}
public Action getAction() {
return storedAction;
}
public String getTimer() {
return storedTimer;
}
#Override
public void writeToParcel(final Parcel out, final int flags) {
super.writeToParcel(out, flags);
out.writeString(storedTimer);
out.writeSerializable(storedAction);
}
// required field that makes Parcelables from a Parcel
public static final Parcelable.Creator<ActionHolderSavedState> CREATOR = new Parcelable.Creator<ActionHolderSavedState>() {
public ActionHolderSavedState createFromParcel(final Parcel in) {
return new ActionHolderSavedState(in);
}
public ActionHolderSavedState[] newArray(int size) {
return new ActionHolderSavedState[size];
}
};
}
}
Is there SOMETHING I am doing wrong? I've spend almost 4 days already on this.
I have a situation very similar to yours, with custom views being added dynamically to the screen and that need to save state when the activity is killed by the OS and recreated later, for example.
I'm overriding onSaveInstanceState on the custom view. It needs to return a Parcelable object. The key is to create a custom class that extends BaseSavedState and stores your data into that Parcelable. It would look somewhat like this:
#Override
protected Parcelable onSaveInstanceState() {
final Parcelable state = super.onSaveInstanceState();
return new ContainerLayoutSavedState(state, data);
}
#Override
protected void onRestoreInstanceState(final Parcelable state) {
if (state instanceof ContainerLayoutSavedState) {
final ContainerLayoutSavedState savedState = (ContainerLayoutSavedState)state;
this.data = savedState.getData();
super.onRestoreInstanceState(savedState.getSuperState());
}
}
public static class ContainerLayoutSavedState extends BaseSavedState {
private String data;
ContainerLayoutSavedState(final Parcelable superState, final String data) {
super(superState);
// Here in this constructor you inject whatever you want to get saved into the Parcelable object. In this contrived example, we're just saving a string called data.
this.data = data;
}
private ContainerLayoutSavedState(final Parcel in) {
super(in);
data = in.readString();
}
public String getData()
return data;
}
#Override
public void writeToParcel(final Parcel out, final int flags) {
super.writeToParcel(out, flags);
out.writeString(data);
}
// required field that makes Parcelables from a Parcel
public static final Parcelable.Creator<ContainerLayoutSavedState> CREATOR = new Parcelable.Creator<ContainerLayoutSavedState>() {
#Override
public ContainerLayoutSavedState createFromParcel(final Parcel in) {
return new ContainerLayoutSavedState(in);
}
#Override
public ContainerLayoutSavedState[] newArray(final int size) {
return new ContainerLayoutSavedState[size];
}
};
} }
Also, don't forget to set IDs to your dynamically added views, so they get re-added to the View tree when you come back.

Singleton constructor crashes my app

I'm testing out the model layer of my application and I want to add an element to a list. But whenever I try to add some data into my data model the application crashes. I cannot find the reason for this.
My code for the data model.
public class DataModel {
private List<Log> logs;
private static DataModel instance;
private Context ctx;
//Singleton constructor
private DataModel()
{
//This makes it crash
logs.add(new Log("1234","sms", 123545, 1, 0));
//Load logs from database - Not done yet.
}
public static DataModel getInstance()
{
if (instance == null)
{
//Creates the instance
instance = new DataModel();
}
return instance;
}
My code for log
public class Log {
private String phonenumber;
private String type;
private long date;
private int incoming;
private int outgoing;
private long id;
//Constructor for incoming sms or call
public Log( String Phonenumber, String Type, long Date, int Incoming, int Outgoing)
{
this.phonenumber = Phonenumber;
this.type = Type;
this.date = Date;
this.incoming = Incoming;
this.outgoing = Outgoing;
}
public long getId()
{
return id;
}
public void setId(long id)
{
this.id = id;
}
public String getPhonenumber()
{
return phonenumber;
}
public void setPhonenumer(String phonenumber)
{
this.phonenumber = phonenumber;
}
public String getType()
{
return type;
}
public void setType(String type)
{
this.type = type;
}
public long getDate()
{
return date;
}
public void setDate(long date)
{
this.date = date;
}
public int getIncoming()
{
return incoming;
}
public void setIncoming(int incoming)
{
this.incoming = incoming;
}
public int getOutgoing()
{
return outgoing;
}
public void setOutgoing (int outgoing)
{
this.outgoing = outgoing;
}
You are not initializing logs. Its null when you execute this statement:
logs.add(new Log("1234","sms", 123545, 1, 0));
Change:
private List<Log> logs;
to:
private List<Log> logs = new ArrayList<Log>();
I see a context in your code, but you don't set it or use it anywhere so maybe you stripped part of your code. In relation to that, if you use it to UI related stuff (and some other cases) I can guarantee you that it will crash your app if you don't reset it every time the screen orientation changes or you change activities.
You have not Instantiated list object
private List<Log> logs;
Update your constructor to this
//Singleton constructor
private DataModel()
{
//This makes it crash
logs = new ArrayList<Log>();
logs.add(new Log("1234","sms", 123545, 1, 0));
//Load logs from database - Not done yet.
}
Now every time you constructor gets called you will get a fresh copy of list object.
Initialize the List before use
you can initialize the List in Constructor as well
public class DataModel {
private List<Log> logs= new ArrayList<Log>();
private static DataModel instance;
private Context ctx;
//Singleton constructor
private DataModel()
{
//This makes it crash
logs.add(new Log("1234","sms", 123545, 1, 0));
int i=0;
//Load logs from database - Not done yet.
}
public static DataModel getInstance()
{
if (instance == null)
{
//Creates the instance
instance = new DataModel();
}
return instance;
}
}
Don't initialize globally logs and also use synchronized getInstance method so that only one instance should get created if two threads are trying to access at the same time.
Use this code:
public class DataModel {
private List<Log> logs;
private static DataModel instance;
private Context ctx;
//Singleton constructor
private DataModel()
{
if(logs == null){
logs = new ArrayList<Log>();
}
logs.add(new Log("1234","sms", 123545, 1, 0));
//Load logs from database - Not done yet.
}
public synchronized static DataModel getInstance()
{
if (instance == null)
{
//Creates the instance
instance = new DataModel();
}
return instance;
}

How to pass params to Android AsnycTaskLoader

I'm trying to pass params into a AsyncTaskLoader. How do I do that?
Currently, I'm putting what I need to pass in in a separate static class. Anyway around this?
public class NewsAsyncTaskLoader extends AsyncTaskLoader<List<Content>> {
private static final DbHelper dbHelper = DbHelperFactory.getDbHelper();
public FeedAsyncTaskLoader(Context context) {
super(context);
}
#Override
public List<Content> loadInBackground() {
List<Content> contents = DbHelper.getStream(FeedSections.getInstance().getCurrentSection());
feed.setContents(contents);
return feed;
}
}
Pass additional parameters into your constructor:
public FeedAsyncTaskLoader(Context context, String moreInfo) {
super(context);
// Do something with moreInfo
}

Use custom ProgressDialog during AsyncTask within Adapter

I use an ArrayAdapter to show items in a ListView. Every row in this ListView owns a button.
Whenever the user clicks on one of these buttons I start an AsyncTask to do some processing in the background.
This is working so far.
Now I want to show a custom ProgressDialog during this time. What puzzles me here is the first parameter of the static convinience method ProgressDialog.show(). Within an activity I usually use "Activityname.this" here. But what should I use in an adapter. I tried context from the adapter (that crashed), context.getApplicationContext and several more. Nothing worked - either crashed or is refused from the compiler.
So my question today: What should I put into this parameter?
Here's a stripped down part of my code:
public class MyAdapter extends ArrayAdapter<MyContainer> {
private class MyAsyncTask extends AsyncTask<String, Void, Boolean> {
#Override
protected void onPreExecute () {
if (!isRunning) {
progressDialog = MyProgressDialog.show(?????,
null,
null,
true,
false);
}
}
#Override
protected Boolean doInBackground(final String... strings) {
boolean rc = false;
if (!isRunning) {
isRunning = true;
//
rc = true;
}
return rc;
}
#Override
protected void onPostExecute(final Boolean result) {
if (progressDialog != null) {
progressDialog.cancel();
}
progressDialog = null;
//
}
}
private class MyOnClickListener implements OnClickListener {
private MyContainer container;
public MyOnClickListener(final MyContainer container) {
this.container = container;
}
public void onClick(final View view) {
if (container != null) {
new MyAsyncTask().execute(container.getUrl());
}
}
private String appName = "";
private ArrayList<MyContainer> containers;
private Context context;
private boolean isRunning;
private int layout;
private MyProgressDialog progressDialog;
private Resources resources;
public MyAdapter(final Context context, final int layout, final ArrayList<MyContainer> containers, final long link_id) {
super(context, layout, containers);
this.context = context;
this.layout = layout;
this.containers = containers;
resources = context.getResources();
appName = resources.getString(R.string.txt_appname);
}
#Override
public View getView(final int position, final View contentView, final ViewGroup viewGroup) {
//
}
}
Thanks in advance.
EDIT: Using the instance variable context from the adapter worked after cleaning the project. Arg! Thanks for your answers.
Hi :D well if you see your constructor of adapter
public MyAdapter(final Context context, final int layout, final ArrayList<MyContainer> containers, final long link_id) {
super(context, layout, containers);
this.context = context;
you pass context so in side of adapter you use context :D to build progress dialog

Categories

Resources