Hi I have a adapter which displays the list of items and all the functions related to setItems, getCount, notifyDataSetChanged.
adapter also has calls to api's through use cases.
Structure is
Adapter -> UseCase -> Repository -> apiLayer
I am aware that fragemnts and activities should not contains calls to api (usecases in my instance).
So should adapter's have api calls (usecases in my instance)
Thanks
R
It is possible, but from a software design point of view I would not recommend it.
The Adapter's responsibility is to connect your Data with the View and make it available to the ListView/RecyclerView. The Adapter should not have any other dependency (knowledge). This will also make it more robust to changes.
So you should consider that only the Activity/Fragment talks to your Presenter and delegates the results from the Presenter to the Adapter.
This will also make (unit) testing (of the Presenter) more easier.
class YourActivity: Activity() {
private val presenter: YourPresenter = // ...
override fun onCreate() {
val adapter: YourAdapter = YourAdapter()
recyclerView.setAdapter(adapter)
val data = presenter.getData()
adapter.submit(data)
}
}
class YourPresenter(private val useCase: UseCase : Presenter() {
fun getData(): List<Data> {
return useCase.fetchData()
}
}
Related
I have an Android app (Java) that makes an api call for shows. After the shows are returned, I need to filter the show arraylist by season & then by episode. I'm currently sorting the list in my fragment because I haven't found a good solution on how to do this in my viewmodel.
This is my call in my vm:
public MutableLiveData<ArrayList<Titles>> getTitlesListLiveData(){
return repository.getTitlesLiveData();
}
And this is how I'm sorting it in my fragment:
titlesViewModel.titlesListLiveData.observe(getViewLifecycleOwner(), titles -> {
for (Titles title : titles) {
titlesList.add(title);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
titlesList.sort(Comparator.comparing(Titles::getSeasonNumber).thenComparing(Titles::getEpisodeNumber));
}
binding.rvTitles.setAdapter(ShowTitlesAdapter);
ShowTitlesAdapter.setShowTitlesList(titlesList);
});
titlesViewModel.getAllTitles(ShowTag);
I find this but I don't understand how I would do it in Java or in my viewmodel. Can anyone help?
UPDATE:
I tried doing this in my ViewModel & it does not work:
public LiveData<List<String>> seasonsListLiveData;
seasonsListLiveData = Transformations.switchMap(titlesListLiveData, titles-> titles.sort(Comparator.comparing(Titles::getSeasonNumber).thenComparing(Titles::getEpisodeNumber));
The error I get: Required type:
LiveData
<List>
Provided:
LiveData
no instance(s) of type variable(s) Y exist so that void conforms to LiveData
Also tried using Transformations.map() & received the same error.
Just use the LiveData Transformations. In your case it could be something like this:
public LiveData<List<Titles>> getTitlesListLiveData()
{
return Transformations.map( repository.getTitlesLiveData(), titles ->
titles.sort(Comparator.comparing(Titles::getSeasonNumber).thenComparing(Titles::getEpisodeNumber))
);
}
Also you don't have to set the adapter every time the data changes:
binding.rvTitles.setAdapter(ShowTitlesAdapter);
You can set it once, for example in the onCreate (when in Activity) or in onCreateView (when in Fragment). Then when the data changes you just call the setShowTitlesList to change the data in adapter
I have recyclerView with 2 view types (item and FOOTER). I need to show my footer on the bottom of screen even if no items or items size is 1. Is it possible to implement it? Now my footer is showing after last item, but I need show footer always on the bottom.
You need a data structure that allows you to do so, and then you need the view holders for supporting it, once that is done handling the conditional flows on the adapter and should be good to go.
Usually, in Kotlin we use a sealed class which allows very good type control
sealed class AdapterRow {
data class RegularItem(/*your values*/) : AdapterRow()
data class FooterItem(/*your values*/) : AdapterRow()
//if the footer is always the same maybe object FooterItem : AdapterRow() is more suitable
}
It's a nice "trick" to have the sealed descendants inside so that way the sealed parent makes a domain space name, then you call them like this AdapterRow.RegularItem(...). If you don't like that, the sealed class has one constraint, descendants must be on the same file.
Then you need a view holder for supporting each type (view holder and the view in the layout if needed). In this case we are gonna use an abstract class to take advantage of the polymorphism and abstract methods
//you could use binding here and then the implementation define the binding type
abstract class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) {
abstract fun bind(row: AdapterRow)
}
And then the children:
class RegularItemViewHolder(view: View) : BaseViewHolder(view) {
override fun bind(row: AdapterRow) {
//validate the data you are receiving is correct
if (row !is AdapterRow.RegularItem) return
//do your data bindings, view findings or whatever
}
}
With the above you can deduce the other view holder, now the adapter methods
getItemViewType(position: Int) {
when(getItem(position)) {
is AdapterRow.RegularItem -> R.layout.REGULAR_ITEM_LAYOUT
is AdapterRow. FooterItem -> R.layout.FOOTER_ITEM_LAYOUT
}
}
onCreateViewHolder(...) {
return when (viewType) {
R.layout.REGULAR_ITEM_LAYOUT -> {
RegularItemViewHolder(...)
}
R.layout.FOOTER_ITEM_LAYOUT {
//same but for footer
}
else -> throw RuntimeException("Unsupported view Holder)
}
}
onBindViewHolder(...) {
holder.bind(getItem(position))
}
The data construction part is the last thing, in somewhere else, your fragment, the view model, etc, you have to create the data structure as you need. By example:
fun makeRows(texts: List<String>): List<AdapterRow> {
val rows = mutableListOf<AdapterRow>()
texts.forEach { text ->
//do some mapping from what ever is your source in this case strings
AdapterRow.RegularItem(...text)
}
//so it doesn't matter if the source of data is empty at the end you always add the footer
rows.add(AdapterRow.FooterItem)
}
And then is just passing the data to the adapter, if you are using ListAdapter
val rows = someClass.makeRows(....)
yourAdapter.submitList(rows)
In my app I have a TabLayout and each of the tabs is represented by a fragment. I have several tables in a database. And for each table I want to have a tab that would display a list of table's contents. To access a database I need to pass in a context but it's only available from the MainActivity. How to access a database instance from each fragment?
Here's some code:
ElectronicsDatabase.java
#Database(entities = {Smartphone.class, Tablet.class,
Laptop.class, VideoGameConsole.class}, version = 1)
public abstract class ElectronicsDatabase extends RoomDatabase {
public abstract SmartphoneDao getSmartphoneDao();
public abstract TabletDao getTabletDao();
public abstract LaptopDao getLaptopDao();
public abstract VideoGameConsoleDao getVideoGameConsoleDao();
private static final String DB_NAME = "products.db";
private static ElectronicsDatabase db;
public static ElectronicsDatabase getInstance(Context context)
{
if (db == null)
{
db =buildDatabaseInstance(context);
}
return db;
}
private static ElectronicsDatabase buildDatabaseInstance(Context context)
{
return Room.databaseBuilder(context, ElectronicsDatabase.class,
DB_NAME).allowMainThreadQueries().build();
}
}
And in the main activity I access it like this:
db = ElectronicsDatabase
.getInstance(getApplicationContext());
In your fragments you can use getActivity() to acccess context of your parent activity.
but i suggest you to use viewModel for accessing to your database.
Try to use ViewModel to access database in any activity or Fragment
ViewModel is a class that is responsible for preparing and managing the data for a UI component (activity or Fragment)
In your case which is needing a Context for DB access via Room, it is better to pass a non-UI Context as to avoid unnecessary information being passed around for no reason leading to possible memory leaks.
You can get access to a non-UI Context which will be called ApplicationContext from your base activity, or your main activity. Simply like this:
Context appContext= getApplicationContext();
Then store it in a Repo class, so you can simply use it anytime you need it again anywhere without worrying about it.
However, if you need a context for something related to drawing on the screen, like inflating an XML for example, then in that case you will need a UI Context as not to lose UI details like your theme for example. In that case you can get the context from inside your fragment using:
getContext() Or getActivity().
I won't go to further details about contexts but,
if you want to learn more about what Context really is you can start from here:
https://medium.freecodecamp.org/mastering-android-context-7055c8478a22
That might be a very late answer, but I believe it might help some in the future:
If you are using multiple fragments and DI, you can create a viewModel for your activity, then inject the desired value into it
#HiltViewModel
class MainActivityViewModel #Inject constructor(
private val repository: PlantsRepository
): ViewModel() {
private val mutableLiveData = MutableLiveData<List<Plant>>()
val liveData: LiveData<List<Plant>> = mutableLiveData // object to observe
init {
viewModelScope.launch {
mutableLiveData.postValue(repository.getAllPlants())
fillExampleData()
}
}
}
(sample for reference)
then you simply use it in your fragments:
class FragmentExampleScreen : Fragment() {
private val sharedViewModel: MainActivityViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
...
sharedViewModel.liveData.observe(viewLifecycleOwner) { idk ->
Log.i("hello", idk.toString())
}
...
}
}
One of my coworkers created an inherit from a RecyclerView and added the logic to create its adapter, defined custom list item attributes, and layout manager inside it.
This is an example of his idea:
class CustomRecyclerView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
init {
loadCustomAttributes()
setDefaultAdapter()
setDefaultLayoutManager()
}
private fun loadCustomAttributes() {
// Load custom attributes: item background, for example.
}
private fun setDefaultAdapter() {
// Define specific Custom Adapter.
}
private fun setDefaultLayoutManager() {
// Define specific Layout Manager.
}
fun setData(data: List) {
// Set data and notify dataset changed.
}
private class CustomAdapter : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
// Specific Adapter.
}
private class ViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
// Specific View Holder.
}
}
I understand his reasons, and it's very reasonable: he wants to plug a View on the XML and just reuse it, without defining the adapter or anything else on the Activity or Fragment; he will just set the data with the method "setData".
But on the other way, I feel that this class is inheriting a RecyclerView just to breaking the design of the RecyclerView and giving too many responsibilities to it. My pain points are:
A RecyclerView shouldn't decide an item attribute (item background colour, for example);
A RecyclerView shouldn’t know about his LayoutManager;
A RecyclerView shouldn't have methods to update the data on the adapter;
A RecyclerView shouldn’t create an instance of his Adapter.
This is an "inheritance over composition" approach.
I tried to find information about it on the internet, but I didn't find anything about good practices when inheriting from a ListView/RecyclerView.
Another idea would be to wrap it inside another layout (ViewGroup or FrameLayout) to encapsulate it. But this would create an unnecessary nested layout, and it will be complicated to test using Espresso (because now the list is private and shouldn't be exposed).
My question is: how to create a reusable RecyclerView using "Composition over Inheritance"?
I need to develop a tag picker, like the one Foursquare uses for tastes, and the one Flipboard uses for "finding new topics."
I came across this library, Foursquare-CollectionPicker at github.
However, it uses a linear layout, which can reduce the performance for numerous child views when scrolling.
Hence, I need to use a recyclerview. Can anyone suggest how to replicate this with a recyclerview? My problem is that for each row in the recyclerview, the column count could be different, based on the size/number of the child views in each row (tags, in this case).
Thank you.
You could use FlexboxLayoutManager in a recycler view. All you need to do is create the layout manager as below. Don't forget to add your own recyclerAdapter to the recycler view as well of course.
FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(context);
layoutManager.setFlexWrap(FlexWrap.WRAP);
recyclerView.setLayoutManager(layoutManager);
https://android-developers.googleblog.com/2017/02/build-flexible-layouts-with.html
https://blog.devcenter.co/unboxing-the-flexboxlayout-a7cfd125f023
If all you need is a recycler view that changes the number of columns (using a standard Google provided GridLayoutManager in RecyclerView), you don't need any custom code at all.
(pseudo code)
Prerequisites
You use a RecylerView with a GridLayoutManager (import androidx.recyclerview.widget.GridLayoutManager)
Your Adapter has a Type (so different viewTypes can inflate different ViewHolders).
You can initialize your grid Layout like:
private lateinit var layoutManager: GridLayoutManager
private val adapter = YourAdapter()
Activity#onCreate(...) {
super.onCreate(savedInstance)
setContentView(...)
layoutManager = GridLayoutManager(this, 2) //defaults to two columns
yourRecyclerView.layoutmanager = layoutManager //set it
yourRecyclerView.adapter = adapter
//Here goes the magic
}
What is the Magic?
Something like this:
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return when (adapter.getItemViewType(position)) {
adapter.viewTypeOneColumn -> 1
adapter.viewTypeTwoColumns -> 2
else -> -1
}
}
}
This obviously assumes you have a "view type" (of any sort)
it can be as simple as:
class YourAdapter : ... {
internal val viewTypeOneColumn = 0
internal val viewTypeTwoColumns = 1
...
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when(viewType) {
viewTypeOneColumn -> ViewHolderForOneColumn(...)
viewTypeTwoColumns -> ViewHolderForTwoColumns(...)
else -> throw IllegalArgumentException("You must supply a valid type for this adapter")
}
}
override fun getItemViewType(position: Int): Int {
return getItem(position).someThingThatClassifiesThem // this obviously depend on what you use to define what each item is...
}
}
And that's all you really need.
I have once created a sample that does this for displaying "ads" inside a RecyclerView: You can check it here (it was updated a while ago but the code still works the same).
https://github.com/Gryzor/GridToShowAds
You will need to write your own layout manager, take a look at these blogs:
http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/
http://simpleandstupid.com/2015/05/01/recyclerview-and-its-custom-layoutmanager/