Share Events in the Open Event Organizer Android App

In the Open Event Organizer Android App, after creating an event the organizer was unable to share it. We handled this challenge and came up with options to share Event with other social media apps. Along with that user can send Email to users containing event description with just a click. All this through a UI that our user will love interacting with. Let’s see how we implemented this.

Specifications

We designed a UI given below which offer four functionalities to the user in a single screen.

  1. The Event Name and Date are shown in the CardView.
  2. User can copy Event External URL by clicking on Copy URL option.
  3. User can send Email containing information about the Event like ( Name, Description, Starting and Ending Date-Time for the Event) via Email by clicking on Email option.
  4. User can share the same information described in point three via other social media/chatting apps etc by clicking on many more option.

The is the Event Model class ia a POJO containing the associated attributes.

We will use Retrofit to fetch Event object from server through a GET request in EventApi class.

@GET(“events/{id}?include=tickets”)
Observable<Event> getEvent(@Path(“id”) long id);

Then we will use the getEvent method of EventRepositoryImpl class to make the request for us using EventApi class and then pass on the Response Event object to the ViewModel by wrapping it in RxJava Observable. We are accepting a boolean field named reload which decdes whether we need to reuse the existing Event object from Local Database  or fetch a new object from server.

@Override
public Observable<Event> getEvent(long eventId, boolean reload) {
Observable<Event> diskObservable = Observable.defer(() ->
repository
.getItems(Event.class, Event_Table.id.eq(eventId))
.filter(Event::isComplete)
.take(1)
);

Observable<Event> networkObservable = Observable.defer(() ->
eventApi
.getEvent(eventId)
.doOnNext(this::saveEvent));

return repository.observableOf(Event.class)
.reload(reload)
.withDiskObservable(diskObservable)
.withNetworkObservable(networkObservable)
.build();
}

In ShareEventVewModel, we are calling the getEvent from EventRepositoryImpl class, construct a LiveData object from it so that UI could observe changes on it. Methods getShareableInformation, getShareableUrl, getShareableSubject provide the shareable information to the UI which is further shared with other apps.

public class ShareEventViewModel extends ViewModel {

protected LiveData<Event> getEvent(long eventId, boolean reload) {
if (eventLiveData.getValue() != null && !reload)
return eventLiveData;

compositeDisposable.add(eventRepository.getEvent(eventId, reload)
.doOnSubscribe(disposable -> progress.setValue(true))
.doFinally(() -> progress.setValue(false))
.subscribe(event -> {
this.event = event;
eventLiveData.setValue(event);
},
throwable -> error.setValue(ErrorUtils.getMessage(throwable))));

return eventLiveData;
}

public String getShareableInformation() {
return Utils.getShareableInformation(event);
}

}

In ShareEventFragment class does the work of binding the UI to the model using data binding. It observes the LiveData objects supplied by presenter and reflect the changes in UI to the LiveData object.

public class ShareEventFragment extends BaseFragment implements ShareEventView {

public void shareEvent() {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, shareEventViewModel.getShareableInformation());
shareIntent.setType(“text/plain”);
startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));
}

public void shareByEmail() {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SENDTO);
shareIntent.setType(“message/rfc822”);
shareIntent.setData(Uri.parse(“mailto:”));
shareIntent.putExtra(Intent.EXTRA_SUBJECT, shareEventViewModel.getEmailSubject());
shareIntent.putExtra(Intent.EXTRA_TEXT, shareEventViewModel.getShareableInformation());
try {
startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));
} catch (android.content.ActivityNotFoundException ex) {
ViewUtils.showSnackbar(binding.getRoot(), “There are no email clients installed”);
}
}

public void copyUrlToClipboard() {
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
String eventUrl = shareEventViewModel.getShareableUrl();
if (eventUrl == null) {
ViewUtils.showSnackbar(binding.getRoot(), “Event does not have a Public URL”);
} else {
ClipData clip = ClipData.newPlainText(“Event URL”, shareEventViewModel.getShareableUrl());
clipboard.setPrimaryClip(clip);
ViewUtils.showSnackbar(binding.getRoot(), “Event URL Copied to Clipboard”);
}
}
}

The layout file contains the Event object bind to the UI using Two way Data Binding. Here is an extract from the layout file. For viewing entire file, please refer here.

References

  1. Official documentation of Retrofit 2.x http://square.github.io/retrofit/
  2. Official documentation for RxJava 2.x https://github.com/ReactiveX/RxJava
  3. Official documentation for ViewModel https://developer.android.com/topic/libraries/architecture/viewmodel
  4. Codebase for Open Event Organizer App https://github.com/fossasia/open-event-orga-app
Continue ReadingShare Events in the Open Event Organizer Android App

Create Call for Speakers with Open Event Organizer Android App

In The Open Event Organizer Android app, we were providing variety of features to the user but the functionality to create call for speakers for an Event was missing. This feature was required to attract speakers from all over the world to participate in the event and make it a success. Theis blog explains how we added this feature to the project us following MVVM Architecture and using libraries like Retrofit, RxJava, Db Flow etc.

Objective

The goal will be to provide an option to the organizer to create Call for Speakers for an Event (if it doesn’t exist already). We will provide the organizer a Floating Action Button in Speakers Call detail layout which will open a fragment which contain all the relevant fields provided by Open Event Server. The organizer can fill up all the fields as per requirement and create the call for speakers.

Specifications

Let’s move on to the implementation details.

First, we will create the model which will contain all the fields offered by server. We are using Lombok library to reduce boilerplate code using annotations. For example, @Data annotation is used to generate getters and setters. @NoArgsConstructor to generate no arguments constructor as the name suggests. Along with that we are using @JonNaming annotation provided by Jackson library to serialize the model using KebabCaseStrategy. Also, the Table annotation provided by Raziz DbFlow library to make a table in database for this model.

@Data
@Type(“speakers-call”)
@NoArgsConstructor
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
@Table(database = OrgaDatabase.class, allFields = true)
public class SpeakersCall {@Id(LongIdHandler.class)
@PrimaryKey
public Long id;@Relationship(“event”)
@ForeignKey(onDelete = ForeignKeyAction.CASCADE)
public Event event;

public String announcement;
public String hash;
public String privacy;
public String startsAt;
public String endsAt;
}

To handle this problem we will be using Retrofit 2.3.0 to make Network Requests, which is a REST client for Android and Java by Square Inc. It makes it relatively easy to retrieve and upload JSON (or other structured data) via a REST based Web Service. Also we will be using other awesome libraries like RxJava 2.1.10 (by ReactiveX) to handle tasks asynchronously, Jackson, Jasminb-Json-Api in an MVVM architecture.

We will make a POST request to the server . So we specify the declaration in SpeakersCallApi.java

@POST(“speakers-calls”)
Observable<SpeakersCall> postSpeakersCall(@Body SpeakersCall speakersCall);

We will use the method createSpeakersCall(SpeakersCall speakersCall) in SpeakersCallRepositoryImpl.java to interact with the SpeakersCallApi and make the network request for us. The response will be passed on to the SpeakersCallViewModel method which calls it.

@Override
public Observable<SpeakersCall> createSpeakersCall(SpeakersCall speakersCall) {
if (!repository.isConnected()) {
return Observable.error(new Throwable(Constants.NO_NETWORK));
}return speakersCallApi
.postSpeakersCall(speakersCall)
.doOnNext(created -> {
created.setEvent(speakersCall.getEvent());
repository
.save(SpeakersCall.class, created)
.subscribe();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}

Next, we will see the code for SpeakersCallViewModel which is using MVVM ViewModel to handle the processing logic for SpeakesCallFragment. Here we are passing SpeakersCallRepository instance in constructor. Methods like getProgress, getError and getSuccess pass on a LiveData Object from ViewModel to Fragment and the latter observes the changes on the object and show these in UI. CreateSpeakersCall method is used to call speakersCallRepository to send a POST request to server asynchronously using Retrofit and RxJava. Further, initialize method is used to set initial values of time and date to SpeakersCall object.

public class CreateSpeakersCallViewModel extends ViewModel {
public void createSpeakersCall(long eventId) {
if (!verify())
return;Event event = new Event();event.setId(eventId);
speakersCallLive.getValue().setEvent(event);

compositeDisposable.add(speakersCallRepository.createSpeakersCall(speakersCallLive.getValue())
.doOnSubscribe(disposable -> progress.setValue(true))
.doFinally(() -> progress.setValue(false))
.subscribe(var -> success.setValue(“Speakers Call Created Successfully”),
throwable -> error.setValue(ErrorUtils.getMessage(throwable))));
}
}

We will create a Fragment class to bind the UI elements to the Model class so that they can be processed by ViewModel. The creation form contains less fields therefore BaseBottomSheetFragment is used. In the onStart method, we are binding several LiveData objects like SpeakersCall, Progress, Error, Success to the UI and then observe changes in them. Using LiveData we don’t have to write extra code to handle screen rotation and to update UI when the binded object is modified.

public class CreateSpeakersCallFragment extends BaseBottomSheetFragment implements CreateSpeakersCallView {
@Override
public void onStart() {
super.onStart();
createSpeakersCallViewModel.getSpeakersCall().observe(this, this::showSpeakersCall);
createSpeakersCallViewModel.getProgress().observe(this, this::showProgress);
createSpeakersCallViewModel.getError().observe(this, this::showError);
createSpeakersCallViewModel.getSuccess().observe(this, this::onSuccess);
createSpeakersCallViewModel.initialize();
}
}

We will design the speaker create form using Two Way Data Binding to bind SpeakersCall object to the UI and use TextInputEditText to take user Input. Along with that we have used Time and Date pickers to make it easier to select Date and Time. For viewing the complete code, please refer here.

Thus, we are able to build a responsive, reactive UI using Android Architectural Components.

References

  1. Official documentation of Retrofit 2.x http://square.github.io/retrofit/
  2. Official documentation for RxJava 2.x https://github.com/ReactiveX/RxJava
  3. Official documentation for ViewModel https://developer.android.com/topic/libraries/architecture/viewmodel
  4. Codebase for Open Event Orga App https://github.com/fossasia/open-event-orga-app
Continue ReadingCreate Call for Speakers with Open Event Organizer Android App

Using Two-Way Data Binding in Open Event Organizer Android App:

Data Binding is the simple approach which relieves developers of repeated findViewById() calls. It is something that every developer must use if not using ButterKnife.

The Open Event Organizer Android App provides options to fill in an extensive set of details while creating an event, or any other entities. The problem at hand is that many of these options are common to many of these entities. For instance, currently the element date-time-picker and text fields are common to elements of different forms, as each one of them requires date-time checkboxes.

We need to be able to <include> a separate smaller and reusable layout file and bind event data to make the code shorter. This would help decreasing unnecessary code base and improving code readability.

We will see how using 2 way data binding and <include> tags in the small PR #929 reduced the code of 112 lines to just 9 lines:

Step 1: Configuration:

The very first step is to configure your project to enable data bindings in your
build.gradle (Module:app) file.

dataBinding should be included as follows:

android {
   // Rest of gradle file…
   dataBinding {
   enabled true
   }    // Rest of gradle file…
}

Step 2: Import and variable tags:

Data Binding uses the tag <data> to signify the data which will be referred to in lambda expressions inside the XML.

We also need to import any class, whose methods we need to use. This can be done using the <import> tag.

Finally, the <variable> tag is used to define any variables that will be referenced in the XML.

 

<data>
  <import type=”android.view.View” />
  <variable
      name=“date”
      type=”String” />
  <variable
      name=“label”
      type=”String“/>
</data>

Step 3: Binding the declared variables:

Data binding recognizes methods of the type set<variable>, where <variable> is event in our case.

We need to use  executePendingBindings();  so that any pending bindings are done and the UI of our app responds correctly as soon as the view data is updated.

@Override
public void showResult(Event event) {
  binding.setEvent(event);
  binding.executePendingBindings();
}

Step 4: Using the declared variables:

Making use of the declared variables is a very simple task and is as simple as a java statement. You can do almost everything that’s possible in the java file, the only constraint being that the used variables are declared in the xml and binded appropriately.

Most of the data binding expressions use data binding to condense the expression to its smallest possible form.

<LinearLayout
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  android:padding=“@dimen/spacing_extra_small”
  android:orientation=“horizontal”
  android:visibility=“@{ picker.checked ? View.VISIBLE : View.GONE }”>

2 Way Data Binding

In case of the Organizer App, we are using 2 way data binding.

Data Binding allows us to do much more than just set text in TextView or create listener in Button. If we want to use EditText and automatically update text variable in java code, we need to use observable fields and two way binding.

Thus, most variables like date, event that we are binding, are Observable fields.

* Sometimes there’s a use case of using a variable declared in another layout file.

For example, in:

<org.fossasia.openevent.app.ui.views.DatePicker
  style=“?attr/borderlessButtonStyle”
  android:layout_width=“wrap_content”
  android:layout_height=“wrap_content”
  android:textColor=“@color/purple_500”
  app:value=“@={ date }” />

The variable date isn’t binded in the java file but the xml files which include the layout time_picker.xml

Using the <include> tag:

The include tag is very simple to use, and we can simply bind the date and label element. The event_create_form.xml binds the variable using the bind attribute like this:

<include
  layout=“@layout/time_picker”
  bind:date=“@={ event.startsAt }”
  bind:label=“@{ @string/starts_at }”/>

The most common error you will face:

Often, when there’s something wrong with the XML, the most common error you will face is:

“Cannot resolve Data Binding class…”

This error is because Android Studio couldn’t generate the Data Binding class for your XML file because of some error. Presently, it doesn’t give much details about what’s wrong, so you’ll have to look for the errors yourselves.

The most common mistake newbie developers make is forgetting to bind the variables appropriately.

References:

Android Developer Guide:

https://developer.android.com/topic/libraries/data-binding/

Continue ReadingUsing Two-Way Data Binding in Open Event Organizer Android App: