Saving state of a dynamically added view through an adapter - android

I've been struggling with this problem for two days and haven't found anything helpful online.
My setup is like this, I've a Gridview which I am populating with images through API. Now each cell of GridView contains a button , when clicked is supposed to add an overlay to the cell. This is being done dynamically depending on the values through webservice. Now the problem is when I scroll up or down the overlay that was added shifts to other cells.How can I tackle this problem?
What I've tried:
I've tried the SparseBooleanArray solution which is used to save the state of the checkboxes through saving the position,but this approach will not solve the problem as I am struggling with keeping the state of the view intact.
What I suspect:
The code adds the overlay through a method which is called on click of the button. I am using the clicked position to get the cell through gridView.getChildat(position) and adding the overlay as a child to it. I think my convertView is not updating that's why the overlay keeps on drawing on different cell positions.
Relevant code:
This is inside my getView method,
if (convertView == null) {
convertView = inflater.inflate(R.layout.grid_image_row, null);
holder = new Holder();
holder.iv_product_image = (ImageView) convertView.findViewById(R.id.id_iv_product_image) ;
RelativeLayout.LayoutParams iv_product_image_params = new RelativeLayout.LayoutParams(imageWidth,imageHeight) ;
holder.iv_product_image.setLayoutParams(iv_product_image_params) ;
iv_product_image_params = null ;
holder.iv_loader_image = (ImageView) convertView.findViewById(R.id.id_iv_loader_image) ;
holder.bt_expand_save_overlay = (CheckBox) convertView.findViewById(R.id.id_bt_expand_save_overlay) ;
holder.tv_productname = (TextView) convertView.findViewById(R.id.id_tv_productname) ;
customFonts.SetSoureLightFont(holder.tv_productname) ;
holder.tv_sellingprice = (TextView) convertView.findViewById(R.id.id_tv_sellingprice) ;
customFonts.SetSoureLightFont(holder.tv_sellingprice) ;
holder.tv_price = (TextView) convertView.findViewById(R.id.id_tv_price) ;
customFonts.SetSoureRegularFont(holder.tv_price) ;
holder.parent_cell = null;
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
EDIT : This is how I am setting tags with the positions...
if (session.isLoggedIn()) {
holder.bt_expand_save_overlay.setTag("1,"+position) ;
int i=0;
for (i=0; i<savedItemsHashMap.size(); i++) {
HashMap<String,String> savedItems = new HashMap<String, String>() ;
savedItems = savedItemsHashMap.get(i) ;
if (savedItems.get(GlobalVariables.KEY_PRODUCT_ID).equals(mapStr.get(GlobalVariables.KEY_ID))) {
holder.bt_expand_save_overlay.setButtonDrawable(R.drawable.wishlist_saved_drawable) ;
break ;
}
}
if (i==savedItemsHashMap.size()) {
holder.bt_expand_save_overlay.setButtonDrawable(R.drawable.wishlist_unsaved_drawable) ;
}
} else {
holder.bt_expand_save_overlay.setTag("0,"+position) ;
}
This is how I am binding click listener inside getView,I am saving the position in the tags.
holder.bt_expand_save_overlay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
String getTagString = (String)v.getTag() ;
String [] items = getTagString.split(",");
selectedExpandButton = Integer.valueOf(items[1]) ;
ExpandSaveForLaterOverlay (selectedExpandButton) ;
}
The ExpandSaveForLaterOverlay adds the view to the selected cell but this does not remain on the same child when scrolled.
Thanks in advance for any help or tips!
Update: I checked the google play music application and they have the same setup. They have a gridview with a button which shows a menu overlay on click. But before scrolling the menu goes invisible, and only after that it scrolls. Is there no way to save the overlay in the view instance itself?

Try to use setTag() on imageview in adapter like
holder.imageView.setTag(position);
And getTag() when you are adding the overlay as a child like
holder.imageView.getTag();
then add overlay.

Ok finally my idea about android architecture was wrong. What I needed was to show the added layout as dropdown. And the added layout should be scrolled when the gridview is scrolled.Which is not possible in any way by available android sdk. I might have to make my own view for such implementations which will keep track of the virtual y and x position of the added overlay.

Related

Radio Buttons in ListView recycling and causing issues

So I have a listview that is built to have each view in the list to have 2 radio buttons. The problem is when the list gets longer than the page the list starts to recycle the views and it also takes along the checked radio buttons. I understand what is causing the problem but I don't really know how to solve this issue and the answers I've looked up online aren't really helping. My problem is a bit unique because I'm using Parse with my adapter so looking up solutions specifically for parse is pretty hard.
Here is my code for my adapter:
// Set up a customized query
ParseQueryAdapter.QueryFactory<AnywallPost> factory =
new ParseQueryAdapter.QueryFactory<AnywallPost>() {
public ParseQuery<AnywallPost> create() {
Location myLoc = (currentLocation == null) ? lastLocation : currentLocation;
ParseQuery<AnywallPost> query = AnywallPost.getQuery();
query.include("user");
query.orderByDescending("PostScore");
query.whereWithinKilometers("location", geoPointFromLocation(myLoc), radius
* METERS_PER_FEET / METERS_PER_KILOMETER);
query.setLimit(MAX_POST_SEARCH_RESULTS);
return query;
}
};
// Set up the query adapter
postsQueryAdapter = new ParseQueryAdapter<AnywallPost>(this, factory) {
#Override
public View getItemView(AnywallPost post, View view, ViewGroup parent) {
if (view == null) {
view = View.inflate(getContext(), R.layout.anywall_post_item, null);
}
//TextView DetailsView = (TextView) view.findViewById(R.id.content_view);
TextView contentView = (TextView) view.findViewById(R.id.content_view);
TextView usernameView = (TextView) view.findViewById(R.id.username_view);
TextView postscoreView = (TextView) view.findViewById(R.id.PostScore);
RadioButton upvote = (RadioButton) view.findViewById(R.id.Upvote);
RadioButton downvote = (RadioButton) view.findViewById(R.id.DownVote);
//DetailsView.setText(post.getDetails());
contentView.setText(post.getText());
usernameView.setText(post.getUser().getUsername());
postscoreView.setText(post.getInt().toString());
upvote.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
post.increment("PostScore", 1);
post.saveInBackground();
}
});
downvote.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//post.decrement();
}
});
return view;
}
};
postsQueryAdapter.setAutoload(false);
postsQueryAdapter.setPaginationEnabled(false);
// Attach the query adapter to the view
ListView postsListView = (ListView) findViewById(R.id.posts_listview);
postsListView.setAdapter(postsQueryAdapter);
So I need these two radio buttons to stay with the post made the entire time but I am still a beginner so I'm not entirely sure how I would go about that. Could anyone provide a solution and explain how it worked? If you need anymore code just let me know. Thank you for you time.
Essentially when you get a new cell, be sure to clear both the radio buttons, then re-check them only if it meets your condition for it to be checked (likely checking the count on the post object). Hard to tell exactly how this will look without knowing more about how you are storing data, but I'd guess something like this:
upvote.setChecked(post.getInt() > 0);
downvote.setChecked(post.getInt() < 0);
Along with the list items I'd carry a list with the state of radio buttons.
So even when the views get recycled you use your own state to check the correct radio button.
A good choice would be a list of Boolean (the boolean wrapper). Where you could keep true for upvote, false for downvote and null for neither.

Android: deleting programmatically added image buttons with ID's

hows it going? I'm creating a little training app for a project, its going fine except for a formatting problem im getting. So, ive a csv file with a name and age for a client. an array is created from this, then I've got a scroll View containing a grid layout and i create Image Buttons from the client array. that's all fine. ive got an add client button at the end of this, the button and its activity work fine, but when you come back to the main screen, the buttons are all screwed up (huge, misplaced etc). So i figured i would loop through and delete all the buttons and repopulate the main screen, except, since i programmatically created them, i cant figure out how to find them to delete them. i tried setting their id's to the index of the array, but then i get a null pointer error.
Function where the buttons are created:
public void fillActivity_main(){
if(listPopulated == false) { // check to see if its aready been created
populateClientList();//fill array with client objects
listPopulated = true;
}
//setup asset manager
AssetManager am = getApplicationContext().getAssets();
//Create the "GridLayout Image Board"
GridLayout buttonBoard = (GridLayout) findViewById(R.id.buttonboard);
int idealWidth = buttonBoard.getWidth(); //get width of the board
int idealHeight = buttonBoard.getHeight() / 2;//same
//create the Listeners, this is a place holder for now but will eventually use SetCurrentClient() (or maybe just switch to Start screen, with the current client?)
View.OnClickListener imageClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
System.out.println("CLICK AT: " + v.getId());
Client temp = clientList[v.getId()];
Intent i = new Intent(getApplicationContext(), DisplayClient.class);
System.out.println(temp.getName());
i.putExtra("name", temp.getName());
System.out.println(i.getStringExtra("name"));
i.putExtra("age", Integer.toString(temp.getAge()));
startActivity(i);
}
};
int j = 0; //used the keep track of the id's we set for the buttons
for (int i = 0; i < clientList.length; i++) {
if (clientList[i] != null) {
//creation and ID setting
ImageButton imgbutton = (ImageButton) new ImageButton(this);
imgbutton.setId(i);
//Layout shit
imgbutton.setImageResource(R.mipmap.ic_launcher);
imgbutton.setMinimumWidth(idealWidth);
imgbutton.setMinimumHeight(idealHeight);
imgbutton.setOnClickListener(imageClickListener);
//check and set image
if(clientList[i].getClientImage().equals(" ")) {
try{
imgbutton.set(am.openFd(clientList[i].getClientImage()));}
catch(Exception ex){
ex.toString();
}
Log.d("ClientImageCheck", "No picture found for " + clientList[i].getName());
}
buttonBoard.addView(imgbutton);
j++;
}
}
//create the new Client Button at the end of all the rest.
Button newClientButton = (Button) new Button(this);
newClientButton.setText("+"); // obvious
newClientButton.setLayoutParams(new LinearLayout.LayoutParams(GridLayout.LayoutParams.WRAP_CONTENT, GridLayout.LayoutParams.WRAP_CONTENT));
newClientButton.setWidth(idealWidth);
newClientButton.setHeight(idealHeight);
View.OnClickListener newClientListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent i = new Intent(getApplicationContext(), CreateClientForm.class);
startActivityForResult(i, 199);
//System.out.println("Doing good so far, leaving the createclient form bnut still in main");
}
}; // create listener
newClientButton.setOnClickListener(newClientListener); // assign listener
buttonBoard.addView(newClientButton); //add the button the buttonBoard, after all the clients have been added
}
Function where i do the deleting:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//Check which request we're responding to
if (requestCode == 199) {
// Make sure request was successful
if (resultCode == RESULT_OK) {
// The user made a name and crap.
Bundle extras = data.getExtras();
String name = extras.getString("name");
int age = extras.getInt("age");
Client temp = new Client(name, age);
addClientToArray(temp);
System.out.println(name + "attempted add to array");
}
for(int i = 0; i<clientList.length; i++ ){
View v = findViewById(i);
((ViewManager) v.getParent()).removeView(v);
}
fillActivityMain();
}
if i've got the logic right, the 'i' in the loop should be the appropriate id. Granted, the teach has kind of thrown us in the deep end for this project, never taken mobile apps or anything, so all this code is the result of me googling issues as i run into them. I've read the basics for Views, intents, etc, but there must be something i'm missing.
I've tried making the gridLayout that the buttons sit on a class variable so i could call it buttonBoard.removeView(i) or something.
ive also tried `
for(int i = 0; i<clientList.length; i++ ){
ImageButton btn = (ImageButton) findViewByid(i);
((ViewManager) v.getParent()).removeView(btn);
}
Can you add the replacement images at the same time that you delete the existing images? If so, try this:
for(int i = 0; i < buttonBoard.getChildCount(); i++) {
ImageButton tempButton = (ImageButton) buttonBoard.getChildAt(i);
tempButton.setVisibility(View.INVISIBLE);
buttonBoard.addView(yourImageButtonHere, i); //adds a new ImageButton in the same cell you are removing the old button from
buttonBoard.removeView(tempButton);
}
This approach should also prevent the GridLayout from rearranging where the children are. I believe the default behavior if you delete a child view is that the GridLayout will re-order the children so there is not empty cell at the beginning of the grid. I hope that makes sense.
There is so much wrong with this approach.
Mainly you don't have to create the ImageButtons manually and add them to the GridLayout. That is what recycled views such as GridView or RecyclerView are for. In fact you should use those to avoid OutOfMemoryError from having too much images in your layout.
But also you cannot just call setId(i) in the for loop. Android holds many ids already assigned and you can never be sure whether the id is safe. (Unless you use View.generatViewId())
And since you only want to remove all views added to your GridLayout why don't you just call removeAllViews() on the buttonBoard?

How to get dynamic CheckBox Id

I have created checkbox dynamically. Based on the require list size just am creating new dynamic check box in a repeated manner and am also setting the Id for that. Now i want to do check it in other loop.
for(int i=0;i<require.size();i++)
{
//From Requirements
requirement=require.get(i);
RelativeLayout rl1 = new RelativeLayout(getActivity());
rl1.setBackgroundResource(R.drawable.listviewdesign);
l1.setOrientation(LinearLayout.VERTICAL);
req1 = new CheckBox(getActivity());
rl1.addView(req1);
req1.setId(Integer.parseInt(requirement.r_id));
Log.i("getid",Integer.toString(req1.getId()));
li.add(Integer.toString(req1.getId()));
}
In this loop am just checking element of li and proj_require1 values. If both are equal then I want to make the CheckBox as checked. For that i have written the code here.
for(int i=0;i<li.size();i++)
{
//li.get(i);
req1 = (CheckBox) container.findViewById(i);
String sr = req1.toString();
for(int j=0;j<proj_require1.size();j++)
{
pr = proj_require1.get(j);
if(sr.equals(pr.rid))
{
req1.setChecked(!req1.isChecked());
}
else
{
req1.setChecked(req1.isChecked());
}
}
}
But my doubt is in first loop am creating the CheckBox based on the size of require object. So every time it creates the CheckBox inside the loop. But in second loop am trying to access the checkbox which am created in the first loop. Could anyone please help me to solve this problem? The only way is i can create the CheckBox in the first loop. I want to access it in other loop. Is it possible?

Android dynamically drawing more than 1000 text view is causing full application to not responding

This is my function which will fetch some 1300 record from SQLite database and draw a series of text view inside the table layout. But when this function starts executing full application is not responding for some time. Table layout is again inside a Scroll View.
Basically I want it to load in back ground.so that remaining application is still responsive
public void LoadAlarmNotifications() {
int SerNo = 0 ;
TextView SerNoTxtVw;
TextView AlarmTxtVw;
TextView TimeOccuredTxtVw;
TextView UploadTxtTxtVw;
Notificationtablelayout.setGravity(Gravity.CENTER);
pastEvent_tbleList = oDatabaseHandler.FetchPastEventDetails(sDiagnosisID);
if(pastEvent_tbleList != null)
{
for(Pastevents_tble PEvents_tble: pastEvent_tbleList)
{
final TableRow row = new TableRow(this.getApplicationContext());
nAlarmType = PEvents_tble.GetAlarmID();
nRowID = PEvents_tble.GetEventNo();
SerNo = PEvents_tble.GetEventNo();
SetAlarmType(nAlarmType); // Set Alarm
tAlarmTime = PEvents_tble.GetStrtTime();
sUploadStatus = PEvents_tble.GetUploadStatus();
row.setId(nRowID);
SerNoTxtVw =new TextView(this.getApplicationContext());
AlarmTxtVw =new TextView(this.getApplicationContext());
TimeOccuredTxtVw=new TextView(this.getApplicationContext());
UploadTxtTxtVw =new TextView(this.getApplicationContext());
SerNoTxtVw.setText(Integer.toString(SerNo));
SerNoTxtVw.setWidth(80);
SerNoTxtVw.setHeight(40);
SerNoTxtVw.setGravity(Gravity.CENTER);
SerNoTxtVw.setPadding(2, 2,2,2);
SerNoTxtVw.setTextColor(Color.parseColor("#FFAADDFF"));
row.addView(SerNoTxtVw);
AlarmTxtVw.setText(sAlarmName);
AlarmTxtVw.setWidth(100);
AlarmTxtVw.setHeight(40);
AlarmTxtVw.setGravity(Gravity.LEFT);
AlarmTxtVw.setPadding(2,2,2,2);
// AlarmTxtVw.setBackgroundColor(Color.LTGRAY);
row.addView(AlarmTxtVw);
TimeOccuredTxtVw.setText(tAlarmTime);
TimeOccuredTxtVw.setWidth(140);
TimeOccuredTxtVw.setHeight(40);
TimeOccuredTxtVw.setGravity(Gravity.CENTER);
TimeOccuredTxtVw.setPadding(2,2,2,2);
// TimeOccuredTxtVw.setBackgroundColor(Color.LTGRAY);
row.addView(TimeOccuredTxtVw);
UploadTxtTxtVw.setText(sUploadStatus);
if(sUploadStatus.equals("Y")){
UploadTxtTxtVw.setText("✔");
UploadTxtTxtVw.setTextColor(Color.parseColor("#FF00FF00"));
}
else if(sUploadStatus.equals("N")){
UploadTxtTxtVw.setText("X");
UploadTxtTxtVw.setTextColor(Color.parseColor("#FFFF0000"));
}else
{
UploadTxtTxtVw.setText("✔");
UploadTxtTxtVw.setTextColor(Color.parseColor("#FFAADDFF"));
}
UploadTxtTxtVw.setWidth(100);
UploadTxtTxtVw.setHeight(40);
UploadTxtTxtVw.setGravity(Gravity.CENTER);
UploadTxtTxtVw.setPadding(2,2,2,2);
// UploadTxtTxtVw.setBackgroundColor(Color.LTGRAY);
row.addView(UploadTxtTxtVw);
Notificationtablelayout.addView(row);
nProgressCounter++;
row.setOnClickListener(new OnClickListener() {
#Override
public void onClick(final View SelectedRowVw) {
if(!bSwitchPopOpen)
{
}
});
}
LoadingTxtView.setVisibility(View.GONE);
bEpisodeLoad = true;
} else
{
bEpisodeLoad = true;
LoadingTxtView.setText("No Snapshot available...");
}
}
pastEvent_tbleList size is 1335. Any idea how to optimize this. Instead of text view is there any other light control?
You should use the Loader pattern to do this so that you only load content that the user will actually see, and you should also reuse the text views on the screen. This is what ListView / CursorLoader were created to solve, so you really ought to reuse these components if at all possible.
You should use listview, because it loads visible area so application will not stop doing calculations and you can scroll up and down and see your texviews

Duplicated layout reference in list view

There is an really weird thing happening with my listview. I am creating an ListView with buttons and an editText.
It's disposed like this: [Button] [EditText] [Button], The buttons works like an "incrementer" and "decrementer" updating the numerical value of EditText in 1 unit per click.
The problem is, when I click in an button, almost every time an editText of another list view element is changed (the editText of the clicked item is also changed). And if I click in a button of this erroneous changed item, it also changes the editText of the first one. They basically have the same reference of buttons and editText, although they have textViews with data, and this data is different between they.
To accomplish that I created and custom adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
convertView = mInflater.inflate(R.layout.lastproduct_row, null);
holder = new ViewHolder();
holder.btnAddQtd = (Button) convertView.findViewById(R.lastproduct_row.btn_add_qtd);
holder.btnSubQtd = (Button) convertView.findViewById(R.lastproduct_row.btn_sub_qtd);
holder.etQuantidade = (EditText) convertView.findViewById(R.lastproduct_row.et_quantidade);
TextView tv;
holder.tvList = new TextView[PRODUCTROW_INT_KEY.length];
for(int i = 0; i < PRODUCTROW_INT_KEY.length; i++) {
tv = (TextView) convertView.findViewById(PRODUCTROW_INT_KEY[i]);
holder.tvList[i] = tv;
}
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
HashMap<String, String> hm = productsList.get(position);
String key = hm.get(CODIGO_KEY);
for(int i = 0; i < PRODUCTROW_INT_KEY.length; i++) {
holder.tvList[i].setText(hm.get(PRODUCTROW_STR_KEY[i]));
}
holder.btnAddQtd.setTag(key+QTD_FLAG+ADD_ACTION);
holder.btnSubQtd.setTag(key+QTD_FLAG+SUB_ACTION);
holder.btnAddQtd.setOnClickListener(handle);
holder.btnSubQtd.setOnClickListener(handle);
if(novosEstoques.containsKey(key)) {
holder.etQuantidade.setText(MyParseFunctions.parseCentesimal(novosEstoques.get(key).getQuantidade()));
}
return convertView;
}
class ViewHolder {
private TextView []tvList;
private Button btnAddQtd, btnSubQtd;
private Button btnAddQtVol, btnSubQtVol;
private EditText etQuantidade, etQtVolume;
}
I added onClick listenners to the buttons, setting their tags with my listView element ID (concatenated with another informations). Then in my event listener I just get the button parent View (an LinearLayout) and get the EditText from that using getViewAt():
#Override
public void onClick(View v) {
String tag = (String) v.getTag();
if(tag.contains(QTD_FLAG)) {
String []info = ((String) v.getTag()).split(QTD_FLAG);
float qtd;
LinearLayout ll = (LinearLayout) v.getParent();
ll.setBackgroundColor(Color.rgb(0, 128, 30));
EditText et = (EditText) ll.getChildAt(2);
qtd = Float.parseFloat(et.getText().toString().replace(",", "."));
if(info[1].equals(ADD_ACTION)) {
qtd++;
}
else if(info[1].equals(SUB_ACTION)) {
if(qtd > 0)
qtd--;
}
Log.d("TESTE", "MODIFICAR KEY = "+info[0]);
et.setText(qtd+"");
}
}
I'm using an setBackgroundColor in this example to confirm that the LinearLayout instance is duplicated in the lisView. When I click an Button, it's painted in 2 different list view item.
Anyone can point me what could be doing this? I have found people with an duplicated ListView item, I don know if that is my case, cause I have TextView's inside my ListView, and they are not equal, only the LinearLayout portion with buttons and editText is "shared".
I make some changes in my getView method and it's working now! It seems that every time the getView method is called i have not guarantee at all that my editTexts will be filled properly and I didn't realize that. So every getView call I make I set the editText value, if the user edit an ET value, I store it in a HashMap to restore in getView, if there is no entry in HashMap for the given editText, then I set it to the default value (zero):
...
if(convertView == null) {
holder = new ViewHolder();
holder.btnAddQtd = (Button) convertView.findViewById(R.lastproduct_row.btn_add_qtd);
holder.btnSubQtd = (Button) convertView.findViewById(R.lastproduct_row.btn_sub_qtd);
holder.etQuantidade = (EditText) convertView.findViewById(R.lastproduct_row.et_quantidade);
//Now it is easier to get etQuantidade reference in button
//click handle, I just have to do:
// public onClick(View v) {
// EditText etButtonAssociated = (EditText) v.getTag();
// ...
// }
holder.btnAddQtd.setTag(holder.etQuantidade);
holder.btnSubQtd.setTag(holder.etQuantidade);
holder.btnAddQtd.setOnClickListener(handle);
holder.btnSubQtd.setOnClickListener(handle);
...
}
else {
...
}
holder.etQuantidade.setTag(key);
if(novosEstoques.containsKey(key)) {
holder.etQuantidade.setText(MyParseFunctions.parseCentesimal(novosEstoques.get(key).getQuantidade()));
}
else {
holder.etQuantidade.setText("0");
}
return convertView;
Israel,
After looking over your code, I was wondering about an implementation decision you have made. Since each Button is "bound" to a particular EditText, have you considered setting the Tag of those Buttons to the EditText? The Tag may be any Object including a UI element. This is especially useful for dynamic UI elements, such as a runtime populated list.
Since this is handled in your Adapter you wouldn't have to worry about duplicate Parents and such. Additionally, you could avoid having to worry about "finding" the control in your onClick() because you would have it (It's the tag). I'm not sure exactly what your project needs are, but this seems like a potentially viable solution, unless you need those Buttons to accomplish other tasks.
Note of Caution
Just make sure that you erase the Tags' references to the EditText when you are done. Otherwise, you run the risk of leaking some memory.
FuzzicalLogic

Categories

Resources