Kategorie: Daily Coding Knowledge

#7 @QueryParams in Retrofit, but only allowing certain sets of parameters. Sealed classes plus interface delegation!

In Square’s Retrofit for Android, when you have a GET endpoint which can take a complex combination of parameters, you can use a  @QueryMap to pass a map of parameters that will be used for the GET request. Something like this:

interface NewsService {
    @GET("/news")
    suspend fun getNews(@QueryMap Map<String, String> options): List<News>
}

But what if you want to make sure the endpoint is only called with certain parameters?

To achieve this, we will use of course sealed classes. The kicker comes now though, combining sealed classes and a great but lesser known feature of kotlin, interface delegation, this can be achieved in a simple and powerful form:

interface NewsService {
    @GET("/news")
    suspend fun getNews(@QueryMap params: QueryParameters): List<News>
}

sealed class NewsSections : Map<String, String> {
    object Home : NewsSections(), Map<String, String> by mapOf("page" to "home")
    object Breaking : NewsSections(), Map<String, String> by mapOf("page" to "breaking", "count" to "12")
}

Now the endpoint can only be called with certain parameters, which can be defined in an easy to read, yet powerfully concise way.

#6 One thing to look out for when using LiveData and Transformations

If you were to unit test LiveData and LiveData transformations you might write a test like this:

class MainViewModel(initialArticle: Article) : ViewModel() {

    private val _articleLiveData = MutableLiveData<Article>()
    val articleLiveData: LiveData<Article> = _articleLiveData
    val articleTitleLiveData: LiveData<String> = Transformations.map(_articleLiveData) { it.title }

    init {
        _articleLiveData.value = initialArticle
    }
}

class MainViewModelTest {

    @get:Rule
    var rule: TestRule = InstantTaskExecutorRule()

    private lateinit var viewModel: MainViewModel

    private val initialArticle =
        Article("first Article", "text of first article. blablalbalb. blalbla. bla.")

    @Before
    fun setup() {
        viewModel = MainViewModel(initialArticle)
    }

    @Test
    fun testArticleEmission() {
        Assert.assertEquals(initialArticle, viewModel.articleLiveData.value)
    }

    @Test
    fun testArticleTitleEmission() {
        Assert.assertEquals(initialArticle.title, viewModel.articleTitleLiveData.value)
    }
}

But it might surprise you that the 2nd test testArticleTitleEmission will fail. Why is that?

Turns out that transformed LiveData does not emit values without an observer. So to make the test pass you need to adjust it in the following way:

@Test
fun testArticleTitleEmission() {
    viewModel.articleTitleLiveData.observeForever { } // This is the important bit!!
    Assert.assertEquals(initialArticle.title, viewModel.articleTitleLiveData.value)
}

Note: It doesn’t matter what your observer does, just that it exists.

Now both tests will pass.

#5 Dispatching a message to a Fragment without tight coupling or an EventBus in Android

Sometimes it can be challenging to send information to a specific fragment without creating a tight coupling.

This can be done with an EventBus, but architecture wise this will create its own problems (a high danger of deeply spaghettified code which is hard to reason about is just one of them).

A better way would be to add something like this to your message dispatching Activity:

data class Message(val someData: String)

interface MessageReceiver {
    fun onReceiveMessage(message: Message)
}

private fun nofityMessageReceivers(message: Message) {
    supportFragmentManager.fragments
            .filterIsInstance(MessageReceiver::class.java)
            .forEach { receiver ->
                receiver.onReceiveMessage(message)
            }
}

Rename the MessageReceiver into something more descriptive and make sure your receiving Fragments implement the interface and you are good to go!

#4 Testing: The disadvantage of static methods

What are static methods?

  • Static methods belong to the class rather than an instance of the class.
  • Static methods are not inherited and thus cannot be overridden.
  • You cannot use static methods for declaring a contract via an interface.

These facts are in themselves not bad.

Why are static methods bad for testing? 

Let’s say we want to test the following createCommand method using JUnit Jupiter:

public class Mapper {
    public Command createCommand(final Event event) {
        return Command.builder()
                .commandUuid(UUID.randomUUID().toString())
                .id(event.getEventId())
                .name(event.getName()).build();
    }
}

@Builder
@Getter
@EqualsAndHashCode
public class Event {
    private String type;
    private String eventId;
    private String name;
}

@Builder
@Getter
@EqualsAndHashCode
public class Command {
    private String commandUuid;
    private String name;
    private String id;
}

But mocking the static UUID.randomUUID() method is not possible with most testing frameworks. This is where the problems starts:

We don’t know what to put as commandUuid because it get’s generated inside the mapping by using a static method. This dependency is hidden for us.

public class TestMapping {

    @Test
    public void testMapping() {
        Mapper mapper = new Mapper();
        String eventId = UUID.randomUUID().toString();
        String name = "name";
        Event event = Event.builder()
                .eventId(eventId)
                .name(name)
                .type("create")
                .build();
        Command command = mapper.createCommand(event);

        Command expected = Command.builder()
                .name(name)
                .id(eventId)
                .commandUuid("Don't know what to do here? ?")
                .build();

        Assertions.assertEquals(command, expected);
    }
}

So what could do we do instead?

  1. add dependencies for Mockito and Mockito JUnit Jupiter support
  2. Create UuidGenerator class with a generateUuid() method (if you want you could even go with an interface, but let’s keep it simple for now)
  3. inject UuidGenerator instance to the Mapper class.
  4. Mock the UuidGenerators generateUuid method to always return the same uuid.

Our code will now look like this:

public class UuidGenerator{
    public String generateUuid(){
       return UUID.randomUUID().toString()
    }
}

public class Mapper {

    private final UuidGenerator uuidGenerator;

    public Mapper(UuidGenerator uuidGenerator) {
        this.uuidGenerator = uuidGenerator;
    }

    public Command createCommand(final Event event) {
        return Command.builder()
                .commandUuid(uuidGenerator.generateUuid())
                .id(event.getEventId())
                .name(event.getName()).build();
    }
}


@ExtendWith(MockitoExtension.class)
public class TestMapping {

    private Mapper mapper;

    @Mock
    private UuidGenerator uuidGenerator;

    @BeforeEach
    public void before() {
        mapper = new Mapper(uuidGenerator);
        String commandId = UUID.randomUUID().toString();
        Mockito.when(uuidGenerator.generateUuid()).thenReturn(commandId);
    }

    @Test
    public void testMapping() {
        String eventId = UUID.randomUUID().toString();
        String name = "name";
        Event event = Event.builder()
                .eventId(eventId)
                .name(name)
                .type("create")
                .build();
        Command command = mapper.createCommand(event);

        Command expected = Command.builder()
                .name(name)
                .id(eventId)
                .commandUuid(uuidGenerator.generateUuid())
                .build();

        Assertions.assertEquals(command, expected);
    }
}

One could argue that we could write a custom equalsData() function or something similar. Or check for all fields separately? But what if we’ll change one of the Event or Command classes? We would always need to think about adjusting these methods and imagine bigger data classes with lots of fields: everything would get very cluttered.

Apart from that this is just an example, this principle is applicable to many more cases.

 

#3 Android Switch Switching without triggering the listener

Sometimes we need to change the calue of a Switch (or SwitchMaterial), without calling the attached listener (to set it programmatically). To achieve this you can write yourself a little handy extension function like this:

fun CompoundButton.changeValueWithoutTriggeringListener(checked: Boolean, listener: CompoundButton.OnCheckedChangeListener) { 
    setOnCheckedChangeListener(null) 
    isChecked = checked 
    setOnCheckedChangeListener(listener) 
}

Unfortunately there is not getter to retrieve the listener, so you will have to store it in a variable and provide it later – or you could write yourself a little helper class like this:

class SwitchHelper(private val switch: CompoundButton) {
    private var listener: CompoundButton.OnCheckedChangeListener? = null

    fun setOnCheckedChangeListener(listener: CompoundButton.OnCheckedChangeListener) {
        this.listener = listener
        switch.setOnCheckedChangeListener(listener)
    }

    fun changeValueWithoutTriggeringListener(checked: Boolean) {
        with(switch) {
            setOnCheckedChangeListener(null)
            isChecked = checked
            setOnCheckedChangeListener(listener)
        }
    }
}

 

 

#2 This is how Kafka Partitioning works

Kafka Partitions: You can configure the number of partitions for a Kafka topic. Inside one partition the order of the records is guaranteed, but ordering is not guaranteed across partitions. Kafka will put records with the same key in the same partition, when no valid partition is specified from the producer.

The producer is responsible for choosing which record to assign to which partition within the topic

 Kafka Consumer distribution to Partitions: The maximum number of consumers in one consumer group equals the number of partitions. Each partition is consumed by exactly one consumer per consumer group but each consumer can consume more than one partition. Kafka will automatically distribute the partitions to all consumers in one group, if one dies it will reassign the partitions to another consumer.

Kafka Partition distribution on Consumers of Consumer groups of different sizes

Replication of topics: Each partition is replicated on a configured number of servers. One of them is the leader and handles all incoming requests to the partition. The others (followers) passively duplicate the data. Writing is only considered as complete once replication is complete. This way Kafka ensures no data loss if the leader fails.

Further reading:

http://kafka.apache.org/documentation/#intro_distribution

#1 @Autowired and how to subclass methods in Kotlin

  1. Since Spring Framework version 4.3 the @Autowired annotation can be omitted on the constructor of a Spring bean, as long as it only defines a single constructor. When defining multiple constructors you still need to mark one with the @Autowired annotation.
  2. In Kotlin it is possible to subclass methods:
class Subclass(val name: String) : () -> String {
    override fun invoke(): String {
        return name
    }
}