337 lines
15 KiB
HTML
337 lines
15 KiB
HTML
|
<!DOCTYPE html>
|
||
|
<html>
|
||
|
<head>
|
||
|
<link rel="Stylesheet" type="text/css" href="style.css">
|
||
|
<link rel="alternate" type="application/rss+xml" title="RSS" href="rss.xml">
|
||
|
<title>First Ticket</title>
|
||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
|
</head>
|
||
|
<body>
|
||
|
|
||
|
|
||
|
|
||
|
<hr />
|
||
|
<p>
|
||
|
<a href="index.html">Index</a>
|
||
|
<hr />
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
last 5 posts
|
||
|
<hr />
|
||
|
</p>
|
||
|
<ul>
|
||
|
<li>
|
||
|
<a href="page.html">Blog Issue</a>
|
||
|
|
||
|
<li>
|
||
|
<a href="page.html">Hello World</a>
|
||
|
|
||
|
</ul>
|
||
|
<p>
|
||
|
<span id="-Date:"></span><strong id="Date:">Date:</strong> 2023/10/30
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
<span id="-Author:"></span><strong id="Author:">Author:</strong> Tristan Ancelet
|
||
|
</p>
|
||
|
|
||
|
<div id="First Ticket"><h1 id="First Ticket" class="header"><a href="#First Ticket">First Ticket</a></h1></div>
|
||
|
<hr />
|
||
|
<p>
|
||
|
I finished my first ticket not too long ago. Was one involving front end work. Which is one of my weakest areas, especially since I've never done front-end work on Android UI before.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
In my previous P&P classes I've only ever done backend and API work (as that's my bread and butter working with enterprise applications at work). It unfortunately took me 2 weeks to complete my REFs (REF-219 & REF-218), which kinda made me feel useless when my classmates finished multiple tickets (2-3+) in the same time it took me to finish one.
|
||
|
</p>
|
||
|
|
||
|
<div id="First Ticket-What was the ticket over?"><h2 id="What was the ticket over?" class="header"><a href="#First Ticket-What was the ticket over?">What was the ticket over?</a></h2></div>
|
||
|
<hr />
|
||
|
<p>
|
||
|
The ticket was about implementing both my REF (REF-218) "Recent Search Functionality" and another REF (REF-219) "Active Search Functionality"
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<div id="First Ticket-What was the ticket over?-Recent Search Functionality (REF-218)"><h3 id="Recent Search Functionality (REF-218)" class="header"><a href="#First Ticket-What was the ticket over?-Recent Search Functionality (REF-218)">Recent Search Functionality (REF-218)</a></h3></div>
|
||
|
<hr />
|
||
|
<p>
|
||
|
This ticket was about implementing the recent search feature. Basically creating a RecyclerView adapter that would load in the users recent searches from disk, and would dynamically add and remove searches from the view as they are added to the search list (and then the adapters NotifyDataSetChanged() method that would cause the RecyclerView to refresh it's views afterwards.
|
||
|
</p>
|
||
|
|
||
|
<div id="First Ticket-What was the ticket over?-Recent Search Functionality (REF-218)-RecyclerView Adapter"><h4 id="RecyclerView Adapter" class="header"><a href="#First Ticket-What was the ticket over?-Recent Search Functionality (REF-218)-RecyclerView Adapter">RecyclerView Adapter</a></h4></div>
|
||
|
<hr />
|
||
|
<p>
|
||
|
Part of the REF was to create a custom adapter for the RecyclerView. Of which gave me the most issue. I've <span id="First Ticket-What was the ticket over?-Recent Search Functionality (REF-218)-RecyclerView Adapter-NEVER"></span><strong id="NEVER">NEVER</strong> done one before, much less done frontend work. Got some help from a classmate Robert Sale. He's a pretty experienced developer compared to me. He pointed me in the right direction and gave me some pseudo-code to help me understand the lifecycle of a RecyclerView. Which I didn't understand before this ticket.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
For this I ended up making a copy-paste version of a generic adapter already present in the project. From there I made it less generic. Created my own version with some hardcoded variables (since there wasn't any need to complicate it anymore that it already was).
|
||
|
</p>
|
||
|
|
||
|
<pre java>
|
||
|
public class RecentSearchAdapter extends RecyclerView.Adapter<RecentSearchAdapter.ViewHolder> {
|
||
|
private static final String TAG = "RecentSearchAdapter";
|
||
|
public class ViewHolder extends RecyclerView.ViewHolder {
|
||
|
@BindView(R.id.recent_search_linear_layout)
|
||
|
LinearLayout layout;
|
||
|
@BindView(R.id.recent_search_textview)
|
||
|
TextView textView;
|
||
|
|
||
|
@BindView(R.id.recent_search_remove_button)
|
||
|
ImageView clearButtonImageView;
|
||
|
|
||
|
|
||
|
private ViewHolder(@NonNull View itemView) {
|
||
|
super(itemView);
|
||
|
ButterKnife.bind(this, itemView);
|
||
|
}
|
||
|
|
||
|
public void setText(String to) {
|
||
|
textView.setText(to);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LinkedList<String> data;
|
||
|
Consumer<Integer> clickedCallback;
|
||
|
Consumer<Integer> deleteCallback;
|
||
|
final static int rootLayout = R.layout.item_recent_searches;
|
||
|
|
||
|
public RecentSearchAdapter( LinkedList<String> data, Consumer<Integer> clickedCallback, Consumer<Integer> deleteCallback){
|
||
|
this.data = data;
|
||
|
this.clickedCallback = clickedCallback;
|
||
|
this.deleteCallback = deleteCallback;
|
||
|
|
||
|
}
|
||
|
|
||
|
@NonNull
|
||
|
@Override
|
||
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||
|
View view = LayoutInflater.from(parent.getContext()).inflate(rootLayout, parent, false);
|
||
|
return new ViewHolder(view);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||
|
ViewHolder Holder = (ViewHolder) holder;
|
||
|
Holder.textView.setText(data.get(position));
|
||
|
Holder.textView.setOnClickListener((viewPos)->{clickedCallback.accept(position);});
|
||
|
Holder.clearButtonImageView.setOnClickListener((viewPos)->{deleteCallback.accept(position);});
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int getItemCount() {
|
||
|
return data.size();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
</pre>
|
||
|
|
||
|
<p>
|
||
|
In the end it made more sense to handle the callback function as a lambda, as that can be formed in the main Fragment and then passed through to the constructor of the adapter.
|
||
|
</p>
|
||
|
|
||
|
<pre java>
|
||
|
LinearLayoutManager recentLayoutManager = new LinearLayoutManager(getContext(), RecyclerView.VERTICAL, false);
|
||
|
recentSearches = new RecentSearchFSUtil(getContext(), "" + CurrentUserManager.getInstance().getEchelonUser().getId());
|
||
|
LinkedList<String> recentSearchList = recentSearches.getRecentSearches();
|
||
|
recentSearchAdapter = new RecentSearchAdapter(
|
||
|
recentSearchList,
|
||
|
(position) ->{
|
||
|
String term = recentSearchList.get(position);
|
||
|
searchBar.setText(term);
|
||
|
loadFirstPage(term);
|
||
|
},
|
||
|
(position) -> {
|
||
|
recentSearchList.remove(position);
|
||
|
recentSearches.deleteSearch(position);
|
||
|
recentSearchAdapter.notifyDataSetChanged();
|
||
|
if (recentSearchAdapter.getItemCount() == 0)
|
||
|
recentSearchLayout.setVisibility(View.GONE);
|
||
|
|
||
|
}
|
||
|
);
|
||
|
recentSearchRV.setLayoutManager(recentLayoutManager);
|
||
|
recentSearchRV.setAdapter(recentSearchAdapter);
|
||
|
</pre>
|
||
|
<p>
|
||
|
<span id="First Ticket-What was the ticket over?-Recent Search Functionality (REF-218)-RecyclerView Adapter-Constructor in onActivityCreated method SearchFragment.java"></span><strong id="Constructor in onActivityCreated method SearchFragment.java">Constructor in onActivityCreated method SearchFragment.java</strong>
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
This would allow for the callbacks to be scoped with access to methods it wouldn't have access to otherwise.
|
||
|
</p>
|
||
|
|
||
|
<div id="First Ticket-What was the ticket over?-The Meeting"><h3 id="The Meeting" class="header"><a href="#First Ticket-What was the ticket over?-The Meeting">The Meeting</a></h3></div>
|
||
|
<hr />
|
||
|
<p>
|
||
|
Unfortunately during on of our bi-weekly meetings with the team, it only took our lead about 20 seconds to find a bug in the feature that I hadn't accounted for. He found this within moments after checking out my branch, which was very embarrasing, but it is what it is.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
In the end the issue that was picked up by the lead was that my feature didn't store searches for any other sources other than physically typing into the search bar at the top of the screen of the app. The fix was to place my two method calls to add the search query and the callback to the loadFirstPage method. This was universally used accross all of the search types (Popular, Recent, Suggested, etc). It was just the one method that was used by all other parts of the search page. It was about 2 minutes and the bug had been fixed before QA could decide to send it back it was an issue lol.
|
||
|
</p>
|
||
|
|
||
|
<pre java>
|
||
|
public void loadFirstPage(String q) {
|
||
|
if (resultsList == null) {
|
||
|
resultsList = new ArrayList<>();
|
||
|
} else {
|
||
|
resultsList.clear();
|
||
|
}
|
||
|
|
||
|
recentSearches.resolveNewSearch(q); //call 1
|
||
|
recentSearchAdapter.notifyDataSetChanged(); //call 2
|
||
|
suggestedLayout.setVisibility(View.GONE);
|
||
|
loading.setVisibility(View.VISIBLE);
|
||
|
resultsList(q).enqueue(new Callback<ContentSearchResultsResponse>() {
|
||
|
@Override
|
||
|
public void onResponse(Call<ContentSearchResultsResponse> call, Response<ContentSearchResultsResponse> response) {
|
||
|
loading.setVisibility(View.GONE);
|
||
|
List<ContentData> data = fetchResults(response);
|
||
|
resultsList = data;
|
||
|
mAdapter.addAll(data);
|
||
|
updateResultsRv();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onFailure(Call<ContentSearchResultsResponse> call, Throwable t) {
|
||
|
loading.setVisibility(View.GONE);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
</pre>
|
||
|
|
||
|
<div id="First Ticket-What was the ticket over?-Active Search Functionality (REF-219)"><h3 id="Active Search Functionality (REF-219)" class="header"><a href="#First Ticket-What was the ticket over?-Active Search Functionality (REF-219)">Active Search Functionality (REF-219)</a></h3></div>
|
||
|
<hr />
|
||
|
<p>
|
||
|
Comparatively, this REF was <span id="First Ticket-What was the ticket over?-Active Search Functionality (REF-219)-MUCH"></span><strong id="MUCH">MUCH</strong> simpler than REF-218. In this REF the active search functionality was to be implemented.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
This is just making sure the correct behavior is implemented when the user begins to start typing in the search bar. Each of the different parts of the application had to react a different way depending on the state of the SearchBar (has focus, and if it has text in it).
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
On my side I just needed to make sure that the recent & popular searches were to be set to GONE when there was text in the search bar & in focus. To complete this I just had to add conditional statements in two parts of SearchFragment.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
In the SearchBar's onSearchBarFocusChangeListener method I just had to make sure to hide it when the search bar was not in focus, or when it didn't have any recent searches to show.
|
||
|
</p>
|
||
|
|
||
|
<pre java>
|
||
|
View.OnFocusChangeListener onSearchBarFocusChangeListener() {
|
||
|
return (v, hasFocus) -> {
|
||
|
searchFragmentState.setIsActiveState(hasFocus);
|
||
|
|
||
|
if (searchFragmentState.isActiveState && searchFragmentState.searchTextLength == 0){
|
||
|
if (recentSearchAdapter.getItemCount() > 0)
|
||
|
recentSearchLayout.setVisibility(View.VISIBLE);
|
||
|
} else {
|
||
|
recentSearchLayout.setVisibility(View.GONE);
|
||
|
}
|
||
|
|
||
|
};
|
||
|
}
|
||
|
</pre>
|
||
|
|
||
|
<p>
|
||
|
On the flip side of handling the behavior of the views based on if text is present in the search bar I put my conditionals in the afterTextChanged method in the onSearchBarTextChangedListener.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
I just had to remember that the recent searches & popular searches would need to be hidden when there is text in the search bar, and shown otherwise. The only exception would be for recent searches, and this is just to make sure it's GONE if there are no recent searches to show.
|
||
|
</p>
|
||
|
|
||
|
<pre java>
|
||
|
@Override
|
||
|
public void afterTextChanged(Editable editable) {
|
||
|
|
||
|
searchFragmentState.setSearchTextLength(editable.length());
|
||
|
if (searchFragmentState.searchTextLength > 0) {
|
||
|
popularSearchLayout.setVisibility(View.GONE);
|
||
|
recentSearchLayout.setVisibility(View.GONE);
|
||
|
suggestedLayout.setVisibility(View.VISIBLE);
|
||
|
} else if (searchFragmentState.searchTextLength == 0) {
|
||
|
popularSearchLayout.setVisibility(View.VISIBLE);
|
||
|
if (recentSearchAdapter.getItemCount() > 0)
|
||
|
recentSearchLayout.setVisibility(View.VISIBLE);
|
||
|
suggestedLayout.setVisibility(View.GONE);
|
||
|
}
|
||
|
}
|
||
|
</pre>
|
||
|
|
||
|
<div id="First Ticket-What was the ticket over?-Unexpected Changes"><h3 id="Unexpected Changes" class="header"><a href="#First Ticket-What was the ticket over?-Unexpected Changes">Unexpected Changes</a></h3></div>
|
||
|
<hr />
|
||
|
<p>
|
||
|
There were a few things I needed to change outside of my REF's to make sure everything looked according to our reference/plans.
|
||
|
</p>
|
||
|
|
||
|
<ol>
|
||
|
<li>
|
||
|
Had to alter one of the layouts becuase (recent_search_item) it was incorrectly laid out and sized. Which caused a lot of incorrect sizing of the recent searches when added to the RecyclerView. (Thanks to Robert for pointing this out and providing me with the correct attribute values)
|
||
|
|
||
|
<li>
|
||
|
Had to alter the "Recent Searches" layout xml in the SearchFragment. It wasn't sized properly and was causing the view to become scrollable instead of expanding in size to fill up the space taken up by items.
|
||
|
|
||
|
<ul>
|
||
|
<li>
|
||
|
This was another issue found by the lead when testing my branch.
|
||
|
|
||
|
</ul>
|
||
|
<li>
|
||
|
Had to add two methods to the RecentSearchFSUtil class that Robert made in conjunction to my REF's. Otherwise his utility worked perfect the rest of the time.
|
||
|
|
||
|
<ul>
|
||
|
<li>
|
||
|
getter method (getRecentSearches) to return the internally stored recentSearches list
|
||
|
|
||
|
<li>
|
||
|
delete method (deleteSearch) to remove a recent search by index in the list.
|
||
|
|
||
|
</ul>
|
||
|
</ol>
|
||
|
<div id="First Ticket-What was the ticket over?-Overall"><h3 id="Overall" class="header"><a href="#First Ticket-What was the ticket over?-Overall">Overall</a></h3></div>
|
||
|
<hr />
|
||
|
<p>
|
||
|
This class is going pretty well atm. It's slow unfortunately, since the actual company is prioritizing their main projects right now. Our features and requests/tickets are dried up with our senior members (and Robert as it seems) taking all of the tickets (and some that aren't even in our branch to do). Not that I'm complaining that I have nothing to do, as I have more than enough work to do at my day job. I rather enjoy not having things to do sometimes.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<div id="First Ticket-People Mentioned in this post"><h2 id="People Mentioned in this post" class="header"><a href="#First Ticket-People Mentioned in this post">People Mentioned in this post</a></h2></div>
|
||
|
<hr />
|
||
|
<table>
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th>
|
||
|
Name
|
||
|
</th>
|
||
|
<th>
|
||
|
Social Media
|
||
|
</th>
|
||
|
<th>
|
||
|
Website
|
||
|
</th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
<tr>
|
||
|
<td>
|
||
|
Robert Sale
|
||
|
</td>
|
||
|
<td>
|
||
|
<a href="https://www.linkedin.com/in/robert-sale-83b5aa212">LinkedIn</a> Discord=@robertmsale
|
||
|
</td>
|
||
|
<td>
|
||
|
<a href="https://www.fieldfab.net/">Fieldfab</a>
|
||
|
</td>
|
||
|
</tr>
|
||
|
</tbody>
|
||
|
</table>
|
||
|
|
||
|
</body>
|
||
|
</html>
|