An Order is generated whenever a user buys a ticket in Open Event Android. It contains all the details regarding the tickets and their quantity, also information regarding payment method and relation to list of attendees, through this blog post we will see how Orders are generated in Open Event Android. Implementing Order system can be divided into following parts
- Writing model class to serialize/deserialize API responses
- Creating TypeConverter for Object used in Model class
- Creating the API interface method
- Wiring everything together
Model Class
Model class server two purpose –
- Entity class for storing orders in room
- Serialize / Deserialize API response
The architecture of the Order Model Class depends upon the response returned by the API, different fields inside the Entity Class defines what different attributes an Order consists of and their data types. Since every Order has a relationship with Event and Attendee we also have to define foreign key relations with them. Given below is the implementation of the Order Class in Open Event Android.
@Type(“order”) @JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class) @Entity(foreignKeys = [(ForeignKey(entity = Event::class, parentColumns = [“id”], childColumns = [“event”], onDelete = ForeignKey.CASCADE)), (ForeignKey(entity = Attendee::class, parentColumns = [“id”], childColumns = [“attendees”], onDelete = ForeignKey.CASCADE))]) data class Order( @Id(IntegerIdHandler::class) @PrimaryKey @NonNull val id: Long, val paymentMode: String? = null, val country: String? = null, val status: String? = null, val amount: Float? = null, val orderNotes: String? = null, @ColumnInfo(index = true) @Relationship(“event”) var event: EventId? = null, @Relationship(“attendees”) var attendees: List<AttendeeId>? = null ) |
We are using Jackson for serializing/deserializing JSON response, @Type(“order”) annotation tells jackson that the following object is for key order in json response. Since we are using this as our room entity class we will also have to add a @Entity annotation to this class. Order contains attendee and event id fields which are foreign keys to other entity classes, this also has to be explicitly mentioned while writing the @Entitty annotation as shown in the snippet above . All relationship must be annotated with @Relationship annotation. All the variables serves as attributes in the order table and key name for json conversions.
The fields of this class are the attributes for the Order Table. Payment mode, country, status are all made up of primitive data type and hence require no type convertors whereas we will have to specify type converter for objects like eventId and List<Attendees>
Type Converter
Type Converter allows us to store any custom object type inside room database. Essentially we break down the Object into smaller primitive data types that Object is composed of and which can be stored by room database.
To create a TypeConverter we have to add a @TypeConverter annotation to it, this tells room that this is a special function. For every custom Object, you have to create two different TypeConverter functions. One takes the Object and converts it into primitive data type and the other takes the primitive data type and constructs your custom Object from it. For the Order data class we discussed in the above section we will need two type converters, for EventId and List<Attendee> objects. We will take the example of List<Attendee>
class ListAttendeeIdConverter { @TypeConverter fun fromListAttendeeId(attendeeIdList: List<AttendeeId>): String { val objectMapper = ObjectMapper() return objectMapper.writeValueAsString(attendeeIdList) } @TypeConverter fun toListAttendeeId(attendeeList: String): List<AttendeeId> { val objectMapper = ObjectMapper() val mapType = object : TypeReference<List<AttendeeId>>() {} return objectMapper.readValue(attendeeList, mapType) } |
A type converter shows how we can store an object in the form of primitive data type by performing some operation on it. Here we can see that List<AttendeeId> Object is converted into String (primitive data type) using jackson object mapper and similarly we will have to restore or recreate the List<AttendeeId> Object from the string converted output of the same. The first function fromListAttendeeId deals with converting Object into string type and toListAttendeeId deals with converting string output to List<AttendeeId> type Object.
Not that we have created a TypeConverter for our custom Object type we have to add it to the Open Event Database. This can be done by simply adding it to @TypeConverters list separated by commas as shown below.
@TypeConverters(EventIdConverter::class, EventTopicIdConverter::class, TicketIdConverter::class, AttendeeIdConverter::class, ListAttendeeIdConverter::class) |
API Interface Method
Till now we have seen how Order body looks like and how we can store it in room database but we would also need an Order API class which specifies which endpoint to hit and what body and response type it is expecting.
Given below is the placeOrder function which hits the Order endpoint (baseURL/orders), with the body as an Order and returns a Single<Order> as a response. Since we are using retrofit for making network requests endpoint path is simply added inside @Path annotation and the body can be passed in parameters of the function using @Body annotation.
interface OrderApi { @POST(“orders”) fun placeOrder(@Body order: Order): Single<Order> } |
OrderService is the class which exposes the OrderApi functions and make it available to its ViewModel.
The placeOrder function inside the service class takes Order as body parameter, when this function is called it makes a call to the API function to place an order with the passed parameter the response of which is inserted into the database (caching of Orders) also returns the same value
class OrderService(private val orderApi: OrderApi, private val orderDao: OrderDao) { fun placeOrder(order: Order): Single<Order> { return orderApi.placeOrder(order) .map { orderDao.insertOrder(it) it } } |
To create an Order from the Fragment or Activity one call implement the following function createOrder.
createOrder calls the service layer function placeOrder and subscribes to this observable. Here we are observing on main thread because all the UI related changes has to be done from the main thread. As soon as we subscribe to the function we also sets progress.value = true this allows us to show a progress bar on the UI this is changed to false once the response is received (see doFinally).
You can find the following function in the AttendeeViewModel class in Open Event Android project
fun createOrder(order: Order) { compositeDisposable.add(orderService.placeOrder(order) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { progress.value = true }.doFinally { progress.value = false }.subscribe({ message.value = “Order created successfully!” Timber.d(“Success placing order!”) }, { message.value = “Unable to create Order!” Timber.d(it, “Failed creating Order”) })) } |
Whenever user fills in details of all the attendees sequence of calls and methods are invoked. Firstly attendees are created for the event with ticket details and the data as provided from the UI. On the successful generation of all the attendees ie when total ticket quantity equals the no of attendees an order object is generated with attendees as the list of attendee previously generated and other details as required, this is then passed to createOrder function which internally interacts with the service layer function to create Order.