Clean Architecture: Cannot map PagingSource<Int, Entities> to PagingSource<RepositoryModel> - android-room

My requirement is to display the notes in pages using clean architecture along with offline suppport.
I am using the Paging library for pagination. And below is the clean architectural diagram for getting notes.
Note: Please open the above image in new tab and zoom to view it clear.
I have four layers UI, UseCase, Repository, and Datasource. I am planning to abstract the internal implementation of the data source. For that, I need to map NotesEntities to another model before crossing the boundary.
class TimelineDao{
#Transaction
#Query("SELECT * FROM NotesEntities ORDER BY timeStamp DESC")
abstract fun getPagingSourceForNotes(): PagingSource<Int, NotesEntities>
}
Current Implementation:
internal class NotesLocalDataSourceImpl #Inject constructor(
private val notesDao: NotesDao
) : NotesLocalDataSource {
override suspend fun insertNotes(notes: NotesEntities) {
notesDao.insert(NotesEntities)
}
override fun getNotesPagingSource(): PagingSource<Int, NotesEntities> {
return notesDao.getPagingSourceForNotes()
}
}
Expected Implementation:
internal class NotesLocalDataSourceImpl #Inject constructor(
private val notesDao: NotesDao
) : NotesLocalDataSource {
override suspend fun insertNotes(notes: NotesRepositoryModel) {
notesDao.insert(NotesRepositoryModel.toEntity())
}
override fun getNotesPagingSource(): PagingSource<Int, NotesRepositoryModel> {
return notesDao.getPagingSourceForNotes().map{ it.toNotesRepositoryModel() }
}
}
I am having an issue mapping the PagingSource<Int, NotesEntities> to PagingSource<Int, NotesRespositoryModel>. As for as I have researched, there is no way to map
PagingSource<Int, NotesEntities> to PagingSource<Int, NotesRespositoryModel>
Kindly let me know if there is a clean way/ workaround way to map the paging source objects. If anyone is sure if there is no way as of now. Please leave a comment as well.
Please Note: I am aware that paging allows transformation for PagingData. Below is code snippet that gets notes in pages. It maps NotesEntities to NotesDomainModel. But then I want to use NotesRespositoryModel instead of NotesEntities in the NotesRespositoryImpl, abstracting the NotesEntities within NotesLocalDataSourceImpl layer.
override fun getPaginatedNotes(): Flow<PagingData<NotesDomainModel>> {
return Pager<Int, NotesEntities>(
config = PagingConfig(pageSize = 10),
remoteMediator = NotesRemoteMediator(localDataSource,remoteDataSource),
pagingSourceFactory = localDataSource.getNotesPagingSource()
).flow.map{ it.toDomainModel() }
}
The solution I have thought of:
Instead of using the PagingSource in Dao directly, I thought of creating a custom PagingSource, that calls the Dao and maps the NoteEntities to LocalRepositoryModel.
But then I need to understand that any updates to the DB will not be reflected in the PagingSource. I need to handle it internally.
Kindly let me know your thoughts on this.

What about creating an implementation of PagingSource that forwards all of the calls to the original PagingSource and performs the mapping, something like this:
class MappingPagingSource<Key: Any, Value: Any, MappedValue: Any>(
private val originalSource: PagingSource<Key, Value>,
private val mapper: (Value) -> MappedValue,
) : PagingSource<Key, MappedValue>() {
override fun getRefreshKey(state: PagingState<Key, MappedValue>): Key? {
return originalSource.getRefreshKey(
PagingState(
pages = emptyList(),
leadingPlaceholderCount = 0,
anchorPosition = state.anchorPosition,
config = state.config,
)
)
}
override suspend fun load(params: LoadParams<Key>): LoadResult<Key, MappedValue> {
val originalResult = originalSource.load(params)
return when (originalResult) {
is LoadResult.Error -> LoadResult.Error(originalResult.throwable)
is LoadResult.Invalid -> LoadResult.Invalid()
is LoadResult.Page -> LoadResult.Page(
data = originalResult.data.map(mapper),
prevKey = originalResult.prevKey,
nextKey = originalResult.nextKey,
)
}
}
override val jumpingSupported: Boolean
get() = originalSource.jumpingSupported
}
Usage would be like this then:
override fun getNotesPagingSource(): PagingSource<Int, NotesRepositoryModel> {
return MappingPagingSource(
originalSource = notesDao.getPagingSourceForNotes(),
mapper = { it.toNotesRepositoryModel() },
)
}
Regarding the empty pages in PagingState - mapping all loaded pages back to original value would be too expensive and room's paging implementation is only using anchorPosition and config.initialLoadSize anyway - see here and here.

Related

How to test a state stored aggregate that doesn't produce events

I want to test a state stored aggregate by using AggregateTestFixture. However I get AggregateNotFoundException: No 'given' events were configured for this aggregate, nor have any events been stored. error.
I change the state of the aggregate in command handlers and apply no events since I don't want my domain entry table to grow unnecessarily.
Here is my external command handler for the aggregate;
open class AllocationCommandHandler constructor(
private val repository: Repository<Allocation>,
) {
#CommandHandler
fun on(cmd: CreateAllocation) {
this.repository.newInstance {
Allocation(
cmd.allocationId
)
}
}
#CommandHandler
fun on(cmd: CompleteAllocation) {
this.load(cmd.allocationId).invoke { it.complete() }
}
private fun load(allocationId: AllocationId): Aggregate<Allocation> =
repository.load(allocationId)
}
Here is the aggregate;
#Entity
#Aggregate
#Revision("1.0")
final class Allocation constructor() {
#AggregateIdentifier
#Id
lateinit var allocationId: AllocationId
private set
var status: AllocationStatusEnum = AllocationStatusEnum.IN_PROGRESS
private set
constructor(
allocationId: AllocationId,
) : this() {
this.allocationId = allocationId
this.status = AllocationStatusEnum.IN_PROGRESS
}
fun complete() {
if (this.status != AllocationStatusEnum.IN_PROGRESS) {
throw IllegalArgumentException("cannot complete if not in progress")
}
this.status = AllocationStatusEnum.COMPLETED
apply(
AllocationCompleted(
this.allocationId
)
)
}
}
There is no event handler for AllocationCompleted event in this aggregate, since it is listened by an other aggregate.
So here is the test code;
class AllocationTest {
private lateinit var fixture: AggregateTestFixture<Allocation>
#Before
fun setUp() {
fixture = AggregateTestFixture(Allocation::class.java).apply {
registerAnnotatedCommandHandler(AllocationCommandHandler(repository))
}
}
#Test
fun `create allocation`() {
fixture.givenNoPriorActivity()
.`when`(CreateAllocation("1")
.expectSuccessfulHandlerExecution()
.expectState {
assertTrue(it.allocationId == "1")
};
}
#Test
fun `complete allocation`() {
fixture.givenState { Allocation("1"}
.`when`(CompleteAllocation("1"))
.expectSuccessfulHandlerExecution()
.expectState {
assertTrue(it.status == AllocationStatusEnum.COMPLETED)
};
}
}
create allocation tests passes, I get the error on complete allocation test.
The givenNoPriorActivity is actually not intended to be used with State-Stored aggregates. Quite recently an adjustment has been made to the AggregateTestFixture to support this, but that will be released with Axon 4.6.0 (the current most recent version is 4.5.1).
That however does not change the fact I find it odd the complete allocation test fails. Using the givenState and expectState methods is the way to go. Maybe the Kotlin/Java combination is acting up right now; have you tried doing the same with pure Java, just for certainty?
On any note, the exception you share comes from the RecordingEventStore inside the AggregateTestFixture. It should only occur if an Event Sourcing Repository is used under the hood (by the fixture) actually since that will read events. What might be the culprit, is the usage of the givenNoPriorActivity. Please try to replace that for a givenState() providing an empty Aggregate instance.

Reactive way of returning `Map<Long,T>`

I am learning reactive. In an MVC project I have a service method (and the controller has the same signature) like this:
#Override
public Map<Long, Question> getQuestions() {
List<Question> questions = questionRepo.findAllByType(Type.A);
return questions.stream()
.collect(Collectors.toMap(q -> q.getId(), q -> q));
}
Resulting in something similar to this:
{
1: {id: 1, ...}
2: {id: 2, ...}
...
}
Now, switching to reactive and kotlin coroutines. What is the proper way to implement this in a reactive way?
This is the signature of the repository:
interface QuestionRepository : CoroutineCrudRepository<Question, Long> {
#Query("select * from Question q where type = :type")
fun findAllByType(type: Type): Flow<Question>
}
Approaches
From what I think so far using Mono<Map<Long,Question>> seems to make no sense as it would require to block for building the inner map.
Flow<Map<Long,Question>> Does not make sense either, because we do not populate multiple maps.
So my best approach for now is not using a Map...
override fun getQuestions(): Flow<Question> {
return questionRepo.findAllByType(Type.A)
}
...but this would require to change the frontend code (it now needs to convert the list to a map).
I also think of
override fun getQuestions(): Flow<Pair<Long?,Question>> {
return questionRepo.findAllByType(Type.A).map { it.id to it }
}
but this would require the frontend to change as well, because the output would look like
[{"first":1,"second":{"id":1, ...}]
Are there other, better approaches? How would you implement it?
UPDATE
added repository.
Assuming the Flow emits elements one at a time that you want to put into a single Map in the client code, then you can collect them into a MutableMap like this and return it.
suspend fun getQuestions(): Map<Long, Question> {
val map = mutableMapOf<Long, Question>()
questionRepo.findAllByType(Type.A)
.collect {
map[it.id] = it
}
return map
}
If your downstream client code is not expecting a suspend function, I guess you need to wrap this in runBlocking, and presumably the downstream code is already handling the fact that this is a long-running function call.
override fun getQuestions(): Map<Long, Question> = runBlocking {
val map = mutableMapOf<Long, Question>()
questionRepo.findAllByType(Type.A)
.collect {
map[it.id] = it
}
map
}

Recursively filter and map a list of properties

I'm using Kotlin reflection to check if attributes that have a certain annotation are null.
Given the following example:
data class DataClass(
#SomeRandomAnnotation
val otherAnnotated: String?,
val inner: InnerClass
)
data class AnotherDataClass(
#SomeRandomAnnotation
val annotatedProperty: String?,
val dataClass: DataClass
) {
fun checkCreditAnalysisConstrain() {
print(checkConstrain(this))
}
}
And the function that checks it:
fun checkConstrain(parentClass: Any): List<String> {
val filter = parentClass::class.memberProperties.filter {
if (memberIsDataClass(it)) checkConstrain(getMemberPropertyInstance(parentClass, it))
hasAnnotation(it) && propertyIsNull(it, parentClass)
}
return filter.map { formatResult(parentClass, it) }
}
The idea is that the function is going to iterate through the attributes of my classes checking if they have the annotation and checking if the value is null.
If the property is a data class, the code evaluates the properties of the childs, recursively.
After that, I map the results, transforming the KProperty's into a simple String that is human readable, containing the class name and the attribute name.
The problem is that the above code does not work as expected. The properties returned are only the properties from the first-level class.
If, instead of doing a filter, I just run a forEach and print the result, I get the expected attributes. So I'm pretty sure it's related to the recurring inside a filter.
Do you see any way of doing this in a more functional way? I'm just concerned I won't need a "temp" list and add values to the list and reset it afterwards.
Your function recursively calls itself, but does nothing with the returned list of that recursive call. That's why you only get results for the top-level class.
Also, in my opinion, you shouldn't rely on side effects happening from your filter call. It probably works, but the function's documentation does not provide a guarantee that it will be called exactly once per item in the collection. So there should be a separate for-loop to do the recursive calls, and the result should be added onto existing results.
fun checkConstrain(parent: Any): List<String> {
val memberProperties = parent::class.memberProperties
var result = memberProperties
.filter { hasAnnotation(it) && propertyIsNull(it, parent) }
.map { formatResult(parent, it) }
memberProperties.filter { memberIsDataClass(it) }
.mapNotNull { getMemberPropertyInstance(parent, it) }
.forEach { result += checkConstrain(it) }
return result
}
You didn't provide code for several of the functions you used. This is what I used for them:
val KProperty<*>.returnTypeClass get() = this.returnType.classifier as? KClass<*>
fun <T> memberIsDataClass(member: KProperty<T>) = member.returnTypeClass?.isData == true
fun <T> getMemberPropertyInstance(parent: Any, property: KProperty<T>) = property.getter.call(parent)
fun <T> hasAnnotation(property: KProperty<T>) = property.annotations.firstOrNull { it.annotationClass == SomeRandomAnnotation::class } != null
fun <T> propertyIsNull(property: KProperty<T>, parent: Any) = getMemberPropertyInstance(parent, property) == null
fun formatResult(parent: Any, property: KProperty<*>) = "$parent's property(${property.name}) is annotated with SomeRandomAnnotation and is null."

How can I create Hashmap<String,Int : String,String : String,String> in Kotlin for sorting

I know this character (:) is meaningless in my statement, but I wanted to explain what I want. I want to sort a lot of hashmaps adding Arraylist and using sortedBy but I cant because my values return strings.
Here is my code:
newReference.addValueEventListener(object : ValueEventListener{
override fun onDataChange(p0: DataSnapshot) {
chatMessages.clear()
for(ds in p0.child(playerIDmatchWhoIs).children){
var hashMap = ds.getValue() as HashMap<String, String>
var datetime = hashMap.get("datetime").toString()
var usermail = hashMap.get("usermail")
var usermessage = hashMap.get("usermessage")
chatMessages.add("${usermail}: ${usermessage}")
recyclerViewAdapter.notifyDataSetChanged()
}
}
})
(I want to sort this hashMap, it has datetime value but is returning string.)
println(hashMap): I/System.out: {datetime=1574807563747, usermessage=jmjgmhg, usermail=1#gmail.com}
I assume that chatMessages is of type List<String>. This is generally bad because you cannot to anything with strings. I would suggest you to create a data class which contains all information about a chat message, like so:
data class ChatMessage(val dateTime: Int, val userMail: String?, val userMessage: String?) : Comparable<ChatMessage> {
override fun compareTo(other: ChatMessage) = this.dateTime.compareTo(other.dateTime)
}
As you can see, this class implements the Comparable<ChatMessage> interface. If you then define the chatMessages list like so
private val chatMessages = mutableListOf<ChatMessage>()
you can call chatMessages.sort() which will then sort the list according to dateTime (see the implementation of compareTo in ChatMessage). The final code would look like that:
data class ChatMessage(val dateTime:Int?, val userMail: String?, val userMessage: String?) : Comparable<ChatMessage> {
override fun compareTo(other: ChatMessage) = this.dateTime.compareTo(other.dateTime)
}
private val chatMessages = mutableListOf<ChatMessage>()
fun yourCode() {
newReference.addValueEventListener(object : ValueEventListener {
/* Use proper variable naming. Nobody will understand, what p0 is, but if you name
it dataSnapshot, everyone knows at a glance. */
override fun onDataChange(dataSnapshot: DataSnapshot) {
chatMessages.clear()
// Again, what is ds exactly? Name it properly.
for (ds in dataSnapshot.child(playerIDmatchWhoIs).children) {
// Kotlin recommends to use val instead of var.
// This way, you know that your variables cannot be modified unless you want them to be modified.
val hashMap = ds.getValue() as HashMap<String, String>
// use indexing instead of the get() method
val dateTime = hashMap["datetime"]
val userMail = hashMap["usermail"]
val userMessage = hashMap["usermessage"]
// TODO: Handle null values properly
chatMessages.add(ChatMessage(dateTime!!.toInt(), userMail, userMessage))
recyclerViewAdapter.notifyDataSetChanged()
}
chatMessages.sort()
}
})
}
This assumes that you want to store your timestamp as an integer. However, I would rather recommend to use a time library like java.time (built into java). In that case, you can use java.time.Instant which has many more possibilities to handle time and all the difficulties to handle time.
Read more about java.time.Instant in the Android docs. If you want to learn how to parse a String to java.time.Instant, this might be interesting.

Using eager loading with specification pattern

I've implemented the specification pattern with Linq as outlined here https://www.packtpub.com/article/nhibernate-3-using-linq-specifications-data-access-layer
I now want to add the ability to eager load and am unsure about the best way to go about it.
The generic repository class in the linked example:
public IEnumerable<T> FindAll(Specification<T> specification)
{
var query = GetQuery(specification);
return Transact(() => query.ToList());
}
public T FindOne(Specification<T> specification)
{
var query = GetQuery(specification);
return Transact(() => query.SingleOrDefault());
}
private IQueryable<T> GetQuery(
Specification<T> specification)
{
return session.Query<T>()
.Where(specification.IsSatisfiedBy());
}
And the specification implementation:
public class MoviesDirectedBy : Specification<Movie>
{
private readonly string _director;
public MoviesDirectedBy(string director)
{
_director = director;
}
public override
Expression<Func<Movie, bool>> IsSatisfiedBy()
{
return m => m.Director == _director;
}
}
This is working well, I now want to add the ability to be able to eager load. I understand NHibernate eager loading can be done by using Fetch on the query.
What I am looking for is whether to encapsulate the eager loading logic within the specification or to pass it into the repository, and also the Linq/expression tree syntax required to achieve this (i.e. an example of how it would be done).
A possible solution would be to extend the Specification class to add:
public virtual IEnumerable<Expression<Func<T, object>>> FetchRelated
{
get
{
return Enumerable.Empty<Expression<Func<T, object>>>();
}
}
And change GetQuery to something like:
return specification.FetchRelated.Aggregate(
session.Query<T>().Where(specification.IsSatisfiedBy()),
(current, related) => current.Fetch(related));
Now all you have to do is override FetchRelated when needed
public override IEnumerable<Expression<Func<Movie, object>>> FetchRelated
{
get
{
return new Expression<Func<Movie, object>>[]
{
m => m.RelatedEntity1,
m => m.RelatedEntity2
};
}
}
An important limitation of this implementation I just wrote is that you can only fetch entities that are directly related to the root entity.
An improvement would be to support arbitrary levels (using ThenFetch), which would require some changes in the way we work with generics (I used object to allow combining different entity types easily)
You wouldn't want to put the Fetch() call into the specification, because it's not needed. Specification is just for limiting the data that can then be shared across many different parts of your code, but those other parts could have drastically different needs in what data they want to present to the user, which is why at those points you would add your Fetch statements.

Resources