Duplicated layout reference in list view - android

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

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 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

Saving state of a dynamically added view through an adapter

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.

I have checkbox, imageview and textview in android, I want to check my checkbox when i click the associated textview

Here is my code.
if (view == null) {
view = lInflater.inflate(
R.layout.list_item_multiple_choice, null);
vHolderMultiple = new ViewHolder();
vHolderMultiple.tv = (TextView) view.findViewById(R.id.tvMultipleChoiceListItem);
vHolderMultiple.cb = (CheckBox) view.findViewById(R.id.cbMultipleListItem);
vHolderMultiple.iv = (ImageView) view.findViewById(R.id.ivMultiChoiceItem);
view.setTag(vHolderMultiple);
}
else {
vHolderMultiple = (ViewHolder) view.getTag();
}
vHolderMultiple.tv.setText(optionArray.get(position).getOptionText());
vHolderMultiple.cb.setChecked(lvAnswers.isItemChecked(position));
if (optionArrayCopy.get(position).getImageUrl() != null && optionArrayCopy.get(position).getImageUrl().length() > 0) {
final Bitmap bmp = loader.loadImageBitmap(optionArray.get(position).getImageUrl(), resDir);
vHolderMultiple.iv.setVisibility(VISIBLE);
if (bmp != null) {
vHolderMultiple.iv.setImageBitmap(bmp);
}
} else
vHolderMultiple.iv.setImageBitmap(null);
Same issue is occurred with checkbox with listview.
Use CheckedTextView UI component
OR
You need to code that manually. Add the setonclicklistner to Textview & pass the checkbox object to it. On net lots of code sample available.
Simply add a OnClickListener to the TextView and check/uncheck the CheckBox inside it. Something like this:
vHolderMultiple.tv.setOnClickListener(new OnClickListener(){
public void onClick(View v){
if(vHolderMultiple.cb.isChecked())
vHolderMultiple.cb.setChecked(false);
else
vHolderMultiple.cb.setChecked(true);
}
});
Note that you must declare cd as final in order to be able to change its state from an OnClick method.
It seems like you are using ListView to show some texts with image and checkbox.
It's better to use a CheckedTextView instead, which is a TextView combined with a CheckBox. And use TextView.setCompoundDrawablesWithIntrinsicBounds() to replace the ImageView.
Then you can enable multiple choice mode by ListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE). And ListView will helps to manage the check state of each row.

Last item click null pointer excepction

listView=(ListView)findViewById(R.id.list);
listView.setOnItemClickListener(new OnItemClickListener(){
#Override
public void onItemClick(AdapterView<?> parent, View viewItem, int position, long arg3) {
if(!itemClicked)
{
viewItem = parent.getChildAt(position);
((Button)viewItem.findViewById(R.id.gov)).setVisibility(View.VISIBLE);
((Button)viewItem.findViewById(R.id.re)).setVisibility(View.VISIBLE);
viewItem.invalidate();
itemClicked=true;
clickedItemPos=position;
TextView text = (TextView)viewItem.findViewById(R.id.item);
ime = text.getText();
}
else
{
viewItem=parent.getChildAt(clickedItemPos);
((Button)viewItem.findViewById(R.id.go)).setVisibility(View.INVISIBLE);
((Button)viewItem.findViewById(R.id.re)).setVisibility(View.INVISIBLE);
viewItem = parent.getChildAt(position);
((Button)viewItem.findViewById(R.id.go)).setVisibility(View.VISIBLE);
((Button)viewItem.findViewById(R.id.re)).setVisibility(View.VISIBLE);
viewItem.invalidate();
clickedItemPos=position;
TextView text = (TextView)viewItem.findViewById(R.id.item);
ime = text.getText();
}
final int[] coordAndCat = FavoriteCoord(ime.toString());
Nullpointer exception happens when I have more elements and when you scroll and click the last item on the listview. How to workaround this?
Note: I'm trying to display 2 buttons in every item that is being clicked. This code works for all clicks on items besides the last one (if there are many elements in the list and you need to scroll)
clickedItemPos isn't defined after else unless clickedItemPos=position has been called earlier. What line does the error happen on?
listView=(ListView)findViewById(R.id.list);
listView.setOnItemClickListener(new OnItemClickListener(){
#Override
public void onItemClick(AdapterView<?> parent, View viewItem, int position, long arg3) {
if(!itemClicked)
{
viewItem = parent.getChildAt(position);
//start >> This might be where your problem is (R.id.gov)
((Button)viewItem.findViewById(R.id.gov)).setVisibility(View.VISIBLE);
//end <<
((Button)viewItem.findViewById(R.id.re)).setVisibility(View.VISIBLE);
viewItem.invalidate();
itemClicked=true;
clickedItemPos=position;
TextView text = (TextView)viewItem.findViewById(R.id.item);
ime = text.getText();
}
else
{
viewItem=parent.getChildAt(clickedItemPos);
((Button)viewItem.findViewById(R.id.go)).setVisibility(View.INVISIBLE);
((Button)viewItem.findViewById(R.id.re)).setVisibility(View.INVISIBLE);
viewItem = parent.getChildAt(position);
((Button)viewItem.findViewById(R.id.go)).setVisibility(View.VISIBLE);
((Button)viewItem.findViewById(R.id.re)).setVisibility(View.VISIBLE);
viewItem.invalidate();
clickedItemPos=position;
TextView text = (TextView)viewItem.findViewById(R.id.item);
ime = text.getText();
}
final int[] coordAndCat = FavoriteCoord(ime.toString());
I think you said R.id.gov instead of R.id.go unless R.id.gov is truly an id you have. That is the only thing I saw 'wrong', please show a stacktrace for the error next time, it would help in debugging =)
EDIT
is clickedItemPos initialized somewhere else? because if it isn't this line: viewItem=parent.getChildAt(clickedItemPos); in your else statement would probably be null if nothing was selected before. I understand you want to use this in order to check for previously clicked items, but if nothing was clicked before, this will never get set to a value and therefore be null try doing this in that else statement to avoid the possible null variable:
else
{
if(clickedItemPos != null){ //only add this if there was a previous clicked item
viewItem=parent.getChildAt(clickedItemPos);
((Button)viewItem.findViewById(R.id.go)).setVisibility(View.INVISIBLE);
((Button)viewItem.findViewById(R.id.re)).setVisibility(View.INVISIBLE);
viewItem = parent.getChildAt(position);
((Button)viewItem.findViewById(R.id.go)).setVisibility(View.VISIBLE);
((Button)viewItem.findViewById(R.id.re)).setVisibility(View.VISIBLE);
viewItem.invalidate();
clickedItemPos=position;
TextView text = (TextView)viewItem.findViewById(R.id.item);
ime = text.getText();
}else{
viewItem = parent.getChildAt(position);
((Button)viewItem.findViewById(R.id.go)).setVisibility(View.VISIBLE);
((Button)viewItem.findViewById(R.id.re)).setVisibility(View.VISIBLE);
viewItem.invalidate();
clickedItemPos=position;
TextView text = (TextView)viewItem.findViewById(R.id.item);
ime = text.getText();
}
}
hopefully that will fix it. In your stack trace it says line 91 in your class is the line causing the error, what variable is located on that line? that is usually a good indicator of what variable is causing the null pointer, but since we don't know the line numbers for your code it is hard to guess where the issue is.
Yes there is repeated code in that solution but you could always make a method to put the repeated code in and call that instead if you want.
Good Luck, hope this helps.

Categories

Resources