Show social links in open event android

This blog will illustrate about how social links are are cached in the Open Event Android Project. We needed social link viewing in the app so that the User is able to reach out to the organizer in a much better way. After that we needed caching to store the social links in our database. Let’s head to the code.

1. Change the social link model

The first step is to change the social link model. We are caching social links using the Room Database. So, we need to add the annotation @Entity to the top of the model to represent the model as a table of type SocialLink in the database. We then add the annotation @PrimaryKey to the id field as every table in a room database requires a primary key for reference.

Social link model code :-

@Type(“social-link”)

@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class)

@Entity

data class SocialLink(

       @Id(IntegerIdHandler::class)

       @PrimaryKey

       val id: Int,

       val link: String,

       val name: String

)

2. Create the SocialLinksDao file

This file lists all the possible methods by which the entity in a database can be reached. These methods help in fetching information from the database, as well as updating information in the database. We implement three methods, insertSocialLinks(), deleteAll() and getAllSocialLinks(). insertSocialLinks() helps us in putting social links in the database. deleteAll() helps in deleting all social links from the database. getAllSocialLinks() helps in fetching all social links of a specific eventId

@Dao

interface SocialLinksDao {

   @Insert(onConflict = OnConflictStrategy.REPLACE)

   fun insertSocialLinks(socialLinks: List<SocialLink>)

   @Query(“DELETE FROM SocialLink”)

   fun deleteAll()

   @Query(“SELECT * from SocialLink WHERE event = :eventId”)

   fun getAllSocialLinks(eventId: Long): Flowable<List<SocialLink>>

}

We receive Social Links in the form of a Flowable while fetching from database. As Flowable cannot handle errors correctly, doOnFinally() will not work and thus doOnNext() is used in the SocialLinksViewModel file.

fun loadSocialLinks(id: Long) {

   compositeDisposable.add(socialLinksService.getSocialLinks(id)

           .subscribeOn(Schedulers.io())

           .observeOn(AndroidSchedulers.mainThread())

           .doOnSubscribe({

               progress.value = true

           }).doOnNext({

               progress.value = false

           }).subscribe({

               socialLinks.value = it

           }, {

               Timber.e(it, “Error fetching Social Links”)

               error.value = “Error fetching Social Links”

           }))

}

3. Change the SocialLinksService file for storing social links in Database

We change the SocialLinksService file. We change it in such a way that social links are fetched from the server, only when they don’t exist in the database ( for a particular eventId )

fun getSocialLinks(id: Long): Flowable<List<SocialLink>> {

  //fetch social links

   val socialFlowable = socialLinksDao.getAllSocialLinks(id)

   return socialFlowable.switchMap {

       if (it.isNotEmpty())

           socialFlowable

       else

           socialLinkApi.getSocialLinks(id)

                   .map {

                       socialLinksDao.insertSocialLinks(it)

                   }

                   .flatMap {

                       socialFlowable

                   }

   }

}

We will go through the code in steps. First of all, we declare a variable, socialFlowable, which is assigned a value of a Flowable<List<SocialLinks>> fetched from the database. The getAllSocialLinks(id) takes eventId as parameter. Secondly, we assign a switchMap which is used to return the value of socialFlowable if it is not empty. So, if there have been some social links fetched from the database, we just return them without even making a network call.

If socialFlowable is actually empty, the else part of the conditional takes care of the network call. getSocialLinks(id) return a Flowable<List<SocialLinks>> object. .map is used to insert all the fetched social links. .flatMap is used to return socialFlowable to the function after the process.

4. Modify the network call in SocialLinkApi file

SocialLinks have relationship with event. To store them in the database, we distinguish each of the SocialLinks using eventId. The eventId will not be fetched in the regular network call, so we modify it.

interface SocialLinkApi {

   @GET(“events/{id}/social-links?include=event&fields[event]=id&page[size]=0”)

   fun getSocialLinks(@Path(“id”) id: Long): Flowable<List<SocialLink>>

}

We thus include event relationship. The JSON response will now contain a an event model inside relationships. The event model will have its id and type (event).

4. Create EventId model

This model will contain the eventId. The model will be of type event.

@Type(“event”)

data class EventId(

       @Id(LongIdHandler::class)

       val id: Long

)

4. Modify the SocialLink model to include relationship with event

We modify the model to include event relationship. As we need to store just the id of the event, we create another class named EventId and use a TypeConverter to convert the EventId model to a primitive type Long while compilation. We also include a foreign key which references the id of the event with the event field in the SociaLink model.

@Type(“social-link”)

@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class)

@Entity(foreignKeys = [(ForeignKey(entity = Event::class, parentColumns = [“id”], childColumns = [“event”], onDelete = CASCADE))])

data class SocialLink(

       @Id(IntegerIdHandler::class)

       @PrimaryKey

       val id: Int,

       val link: String,

       val name: String,

       @ColumnInfo(index = true)

       @Relationship(“event”)

       var event: EventId? = null

)

4. Create the TypeConverter

We create the TypeConverter for converting EventId to just the id (Long).

class EventIdConverter {

   @TypeConverter

   fun fromEventId(eventId: EventId): Long{

       return eventId.id

   }

   @TypeConverter

   fun toEventId(id: Long): EventId{

       return EventId(id)

   }

}

4. Modify the Modules.kt file and the OpenEventDatabase

In the OpenEventDatabase file, we include the SocialLink entity to the Database. The annotation @TypeConverters is used to specify the type of TypeConverter to be used. Here we use EventIdConverter. In the abstract class, we include a function socialLinksDao() of type SocialLinksDao. It will fetch all the available functions from the file.

@Database(entities = [Event::class, User::class, SocialLink::class, Ticket::class], version = 1)

@TypeConverters(EventIdConverter::class)

abstract class OpenEventDatabase : RoomDatabase() {

   abstract fun eventDao(): EventDao

   abstract fun userDao(): UserDao

   abstract fun ticketsDao(): TicketsDao

   abstract fun socialLinksDao(): SocialLinksDao

}

In the Modules.kt file we add EventId class to the JSONAPIConverterfactory.

Retrofit.Builder()

       .client(get())

       .addCallAdapterFactory(RxJava2CallAdapterFactory.create())

       .addConverterFactory(JSONAPIConverterFactory(objectMapper, Event::class.java, User::class.java, SignUp::class.java, Ticket::class.java, SocialLink::class.java, EventId::class.java))

       .addConverterFactory(JacksonConverterFactory.create(objectMapper))

       .baseUrl(baseUrl)

       .build()

In the same file, we add another factory object. This is about the fetching of the function of socialLinksDao in the OpenEventDatabase file.

factory {

   val database: OpenEventDatabase = get()

   database.socialLinksDao()

}

Thus, after all the modifications, we are able to achieve the feat of caching of social links in the app successfully!

References

Tags: GSoC18, FOSSASIA, Open Event, Android, SocialLinks Caching