Content Submission

This documentation explains the mechanics of submitting user-generated content (UGC) to Bazaarvoice using the Mobile SDK.

Introduction

Use the Bazaarvoice Mobile SDKs to enable Conversations functionality, such as Ratings and Reviews. The Conversations module provides an easy-to-use wrapper around the Conversations API. Contact Bazaarvoice to set up Conversations.

📘

You will need your apiKeyConversations to implement and use Conversations with Mobile SDKs.

Installation instructions

With Gradle

Include the Maven Central repository and add the appropriate Bazaarvoice Mobile SDK modules to your dependencies:

dependencies {  
    implementation 'com.bazaarvoice.bvandroidsdk:conversations:{BV_SDK_VERSION}.+'  
    implementaion 'com.google.android.gms:play-services-ads:9.{YOUR_PLAY_SERVICES_VERSION}'  
    implementation 'com.android.support:recyclerview-v7:25.{YOUR_SUPPORT_LIBRARY_VERSION}'  
}  
repositories {  
    mavenCentral()  
}

Replace the {tokens}, including brackets, with the appropriate values. Refer to the Installation guide for {BV_SDK_VERSION}.

Extending the application

Create a class that extends android.app.Application, and initialize the Mobile SDK using its builder.

You can find a full list of build options in BVSDK.java.

public class MyApplication extends Application {
@Override
public void onCreate(){
    super.onCreate();
    BVSDK.builder(application, BazaarEnvironment.PRODUCTION)
      .logLevel(BVLogLevel.VERBOSE) // Optional: Set the log level. Default level is ERROR.
      .dryRunAnalytics(false)       // Optional: Set analytics to log only, not make a network request. Default is false.
      .okHttpClient(okHttpClient)   // Optional: Use you own okHttpClient instance
      .build();
  }
}

App manifest file

Add the following to your AndroidManifest.xml file.

<!-- set the name of the class for the application entry -->

<application
    android:name=".MyApplication">  
</application>

Using Conversations Submission API

After you install the Mobile SDK, complete the following steps to submit user-generated content.

Step 1: Create a BVConversationsClient

Use an instance of the BVConversationsClient to add to all of your Conversations Submission API requests. You should keep an instance of this object to handle all your Conversations requests as shown in the examples below.

final BVConversationsClient client = new BVConversationsClient.Builder(BVSDK.getInstance()).build(); // Use one instance

For more information on setting up the BVSDK instance, see Configuring the BVSDK.

For more information on supporting multiple API keys at runtime, see Multi Config

Step 2: Create an AuthenticationProvider

Choose one of the two classes that implement AuthenticationProvider, and create an instance to add to all of your Conversations Submission API requests. You should keep an instance of this object to handle all your Conversations requests as shown in the examples below.

final AuthenticationProvider authProvider = new BVHostedAuthenticationProvider(userEmailAddress, callbackUrl); // for part 1 for BV Hosted Auth clients  
final AuthenticationProvider authProvider = new BVHostedAuthenticationProvider(uas); // for part 2 for BV Hosted Auth clients  
// or  
final AuthenticationProvider authProvider = new SiteAuthenticationProvider(uas); // for Site Auth clients

For more information on which type you are configured for, and how to use these different AuthenticationProviders, see Conversations Authentication.

Step 3: View form options

For each option available in a submission form, there is a boolean flag determining whether it is a required field or not. You can discover this by iterating through the available form fields, which can be found by making a preview submission request.

These form fields may also contain extra information, such as available submission values, and keys for fields specific to your config such as context data values, etc.

Parsing form data

For more information on how the API processes Form Data please see: Submission Fundamentals: Request Form Information

final ReviewSubmissionRequest formRequest = new ReviewSubmissionRequest
  .Builder(Action.Form, "productId")
  .authenticationProvider(authProvider)
  .build();
 
client.prepareCall(formRequest).loadAsync(new ConversationsSubmissionCallback<ReviewSubmissionResponse>() {
  @Override
  public void onSuccess(ReviewSubmissionResponse response) {
    final List<FormField> formFields = response.getFormFields();
    for (FormField formField : formFields) {
      final String key = formField.getId(); // the key that must be used for submission
      final boolean isRequired = formField.isRequired(); // Whether this field is required or optional
      final String label = formField.getLabel(); // Configured label to display to user for this field
      final FormInputType inputType = formField.getFormInputType();
 
      if (inputType == FormInputType.TextAreaInput) {
        final int minLength = formField.getMinLength();
        final int maxLength = formField.getMaxLength();
      }
 
      if (inputType == FormInputType.SELECT) {
        final List<FormFieldOption> options = formField.getFormFieldOptions();
        for (FormFieldOption option : options) {
          final String label = formField.getLabel(); // User facing option string
          final String value = formField.getValue(); // the value that must be used for submission
        }
      }
    }
  }
 
  @Override
  public void onFailure(ConversationsSubmissionException exception) {
    // Might contain failure info specific to your configuration
    // that is required even for fetching a form. Otherwise missing
    // form field errors do not exist for Action.Form requests
  }
});

Step 4: Send submission request

Submitting reviews

To submit content, form the proper ConversationsSubmissionRequest subclass, and send it through the BVConversationsClient. All parameters to the Builder constructors are required, and all other options may or may not be required per your config. Consult the FormData above to answer which are required.

final ReviewSubmissionRequest previewSubmission = new ReviewSubmissionRequest
  .Builder(Action.Submit, productId)
  .authenticationProvider(authProvider)
  .rating(4)
  .title(userMadeTitle)
  .reviewText(userReviewText)
  .isRecommended(true)
  .sendEmailAlertWhenCommented(true)
  .netPromoterScore(3)
  .netPromoterComment(promotionComment)
  .addFreeFormTag(questionId, value)
  .addPredefinedTag(questionId, tagId, value)
  .addAdditionalField(key, value)
  .addContextDataValueString(contextDataValueId, value)
  .addRatingSlider(questionName, value)
  .addRatingQuestion(questionName, value)
  .addVideoUrl(youTubeLink, videoCaption) // YouTube video link if your config supports it
  .build();
 
final BVConversationsClient client = new BVConversationsClient.Builder(BVSDK.getInstance()).build(); // Use one instance
client.prepareCall(previewSubmission).loadAsync(new ConversationsSubmissionCallback<ReviewSubmissionResponse>() {
  @Override
  public void onSuccess(ReviewSubmissionResponse response) {
    // Make sure to send with Action.SUBMIT for it to fully complete
    // All good
  }
  @Override
  public void onFailure(ConversationsSubmissionException exception) {
    // How to handle described in next step
  }
});

Progressive Submission

To submit content, form the proper ConversationsSubmissionRequest subclass, and send it through the BVConversationsClient. All parameters to the Builder constructors are required, and all other options may or may not be required per your config. Consult the Progressive Submission Documentation for more details about this api.

Progressive submission should be used in scenarios where clients want to display multiple review request forms for a number of items. Additionally, progressive submission supports partial or incremental submissions across different devices.

A progressive submission is a multiple request step process. The first step is to construct an ‘InitiateProgrssiveSubmitRequest’. This request will initiate a progressive Submission session for the products submitted.

final InitiateSubmitRequest initiateSubmitRequest = new InitiateSubmitRequest
        .Builder(productIds, locale)
        .extended(true)
        .authenticationProvider(authProvider)
        .build();
 
final BVConversationsClient client = new BVConversationsClient.Builder(BVSDK.getInstance()).build(); // Use one instance
 
client.prepareCall(initiateSubmitRequest).loadAsync(new ConversationsSubmissionCallback<InitiateSubmitResponse>() {
  
  @Override
 
  public void onSuccess(InitiateSubmitResponse
 
 response)
 
{     // All good   }
  @Override
 
  public void onFailure(ConversationsSubmissionException exception) {     // How to handle described in next step   }
 
});

An InitiateSubmitResponse contains the submissionSessionToken for each product. This token is used for submitting content and making updates to a review for the associated product. The next step is to collect the fields to submit a review and then construct a ‘ProgressiveSubmitReview’.

Map<String, Object> submissionFields = new HashMap<>();
submissionFields.put("rating", 4);
submissionFields.put("agreedtotermsandconditions", true);
 
ProgressiveSubmitRequest progressiveSubmissionRequest = new ProgressiveSubmitRequest.Builder( productId, submissionSessionToken, locale)
        .submissionFields(submissionFields)
        .authenticationProvider(authProvider)
        .build()
 
final BVConversationsClient client = new BVConversationsClient.Builder(BVSDK.getInstance()).build(); // Use one instance
 
client.prepareCall(progressiveSubmissionRequest).loadAsync(new ConversationsSubmissionCallback< ProgressiveSubmitResponse >()
 
{   @Override   public void onSuccess(ProgressiveSubmitResponse  response) \{     // All good   }
  @Override
 
  public void onFailure(ConversationsSubmissionException exception)
 
{     // How to handle described in next step   }
});

Photos

To submit a photo with a ProgressiveSubmission you will need to attach a photo’s url to the progressiveSubmission’s SubmissionFields after uploading the photo using the ‘PhotoSubmissionRequest’

Map<String, Object> submissionFields = new HashMap<>();
submissionFields.put("photourl_1", photourl_1);
submissionFields.put("photourl_2", photourl_2);

PhotoUpload

To submit content, form the proper ConversationsSubmissionRequest subclass, and send it through the BVConversationsClient. All parameters to the Builder constructors are required, and all other options may or may not be required per your config.

 * Final File file = readFile("bv_sample_image.png", *this.getClass());final PhotoUploadRequest photoUploadRequest = new PhotoUploadRequest.Builder(new PhotoUpload(file, "comments", PhotoUpload.ContentType.REVIEW))
        .build(); 
 
final BVConversationsClient client = new BVConversationsClient.Builder(BVSDK.getInstance()).build(); // Use one instance
 
client.prepareCall(photoUploadRequest).loadAsync(new ConversationsSubmissionCallback< PhotoUploadResponse  >() {
 
  @Override
 
  public void onSuccess(PhotoUploadResponse
 
 response)
 
{     // All good                String photoUrl = response._photo_._content_._normalUrl_   }
  @Override
 
  public void onFailure(ConversationsSubmissionException exception)
 
{     // How to handle described in next step   }
});

Submitting review comments

To submit content, form the proper ConversationsSubmissionRequest subclass, and send it through the BVConversationsClient. All parameters to the Builder constructors are required, and all other options may or may not be required per your config. Consult the FormData above to answer which are required.

final CommentSubmissionRequest previewSubmission = new CommentSubmissionRequest
  .Builder(Action.Submit, reviewId, commentText)
  .authenticationProvider(authProvider)
  .title(titleText)
  .build();
 
final BVConversationsClient client = new BVConversationsClient.Builder(BVSDK.getInstance()).build(); // Use one instance
client.prepareCall(previewSubmission).loadAsync(new ConversationsSubmissionCallback<CommentSubmissionResponse>() {
  @Override
  public void onSuccess(CommentSubmissionResponse response) {
    // Make sure to send with Action.SUBMIT for it to fully complete
    // All good
  }
  @Override
  public void onFailure(ConversationsSubmissionException exception) {
    // How to handle described in next step
  }
});

Submitting questions

To submit content, form the proper ConversationsSubmissionRequest subclass, and send it through the BVConversationsClient. All parameters to the Builder constructors are required, and all other options may or may not be required per your config. Consult the FormData above to answer which are required.

final QuestionSubmissionRequest previewSubmission = new QuestionSubmissionRequest
  .Builder(Action.Submit, productId)
  .authenticationProvider(authProvider)
  .questionSummary(summaryText)
  .questionDetails(detailText)
  .isUserAnonymous(true)
  .sendEmailAlertWhenAnswered(true)
  .build();
 
final BVConversationsClient client = new BVConversationsClient.Builder(BVSDK.getInstance()).build(); // Use one instance
client.prepareCall(previewSubmission).loadAsync(new ConversationsSubmissionCallback<QuestionSubmissionResponse>() {
  @Override
  public void onSuccess(QuestionSubmissionResponse response) {
    // Make sure to send with Action.SUBMIT for it to fully complete
    // All good
  }
  @Override
  public void onFailure(ConversationsSubmissionException exception) {
    // How to handle described in next step
  }
});

Submitting answers

To submit content, form the proper ConversationsSubmissionRequest subclass, and send it through the BVConversationsClient. All parameters to the Builder constructors are required, and all other options may or may not be required per your config. Consult the FormData above to answer which are required.

final AnswerSubmissionRequest previewSubmission = new AnswerSubmissionRequest
  .Builder(Action.Submit, questionId, answerText)
  .authenticationProvider(authProvider)
  .userNickname(userNickname)
  .build();
 
final BVConversationsClient client = new BVConversationsClient.Builder(BVSDK.getInstance()).build(); // Use one instance
client.prepareCall(previewSubmission).loadAsync(new ConversationsSubmissionCallback<AnswerSubmissionResponse>() {
  @Override
  public void onSuccess(AnswerSubmissionResponse response) {
    // Make sure to send with Action.SUBMIT for it to fully complete
    // All good
  }
  @Override
  public void onFailure(ConversationsSubmissionException exception) {
    // How to handle described in next step
  }
});

🚧

You cannot submit feedback for syndicated content. Make sure to check the IsSyndicated flag before you display your feedback UI.

Submitting feedback

To submit content, form the proper ConversationsSubmissionRequest subclass, and send it through the BVConversationsClient. All parameters to the Builder constructors are required, and all other options may or may not be required per your config. Consult the FormData above to answer which are required.

final FeedbackSubmissionRequest previewSubmission = new FeedbackSubmissionRequest
  .Builder(contentId)
  .authenticationProvider(authProvider)
  .feedbackType(FeedbackType.HELPFULNESS) // You can also flag for inappropriateness.
  .feedbackContentType(FeedbackContentType.REVIEW)
  .feedbackType(FeedbackType.HELPFULNESS)
  .feedbackVote(FeedbackVoteType.POSITIVE)
  .reasonFlaggedText(reasonText)
  .build();
 
final BVConversationsClient client = new BVConversationsClient.Builder(BVSDK.getInstance()).build(); // Use one instance
client.prepareCall(previewSubmission).loadAsync(new ConversationsSubmissionCallback<FeedbackSubmissionResponse>() {
  @Override
  public void onSuccess(FeedbackSubmissionResponse response) {
    // Make sure to send with Action.SUBMIT for it to fully complete
    // All good
  }
  @Override
  public void onFailure(ConversationsSubmissionException exception) {
    // How to handle described in next step
  }
});

Step 5: Handle errors

For any Action.Preview or Action.Submit request that was not successful, you can parse out the reason programmatically from the ConversationsSubmissionException object in the onFailure callback.

final ReviewSubmissionRequest submitRequest = new ReviewSubmissionRequest
  .Builder(Action.Submit, "productId")
  .authenticationProvider(authProvider)
  .build();
 
client.prepareCall(previewSubmissionRequest).loadAsync(new ConversationsSubmissionCallback<ReviewSubmissionResponse>() {
  @Override
  public void onSuccess(ReviewSubmissionResponse response) {
    // Make sure to send with Action.SUBMIT for it to fully complete
    // All good
  }
 
  @Override
  public void onFailure(ConversationsSubmissionException exception) {
    // This list of errors handles things like missing api key, etc.
    final List<Error> genericErrors = exception.getErrors();
    for (Error error : genericErrors) {
      final ErrorCode code = error.getErrorCode();
      final String message = error.getMessage();
 
      switch (code) {
        // common errors
        case ERROR_BAD_REQUEST:
        case ERROR_ACCESS_DENIED:
        case ERROR_PARAM_INVALID_API_KEY:
        case ERROR_PARAM_INVALID_LOCALE:
        case ERROR_REQUEST_LIMIT_REACHED:
        case ERROR_UNSUPPORTED: handleCommonError(); break;
 
        // display specific errors
        case ERROR_PARAM_INVALID_CALLBACK:
        case ERROR_PARAM_INVALID_FILTER_ATTRIBUTE:
        case ERROR_PARAM_INVALID_INCLUDED:
        case ERROR_PARAM_INVALID_LIMIT:
        case ERROR_PARAM_INVALID_OFFSET:
        case ERROR_PARAM_INVALID_SEARCH_ATTRIBUTE:
        case ERROR_PARAM_INVALID_SORT_ATTRIBUTE: handleDisplayError(); break;
 
        // submission specific errors
        case ERROR_DUPLICATE_SUBMISSION:
        case ERROR_PARAM_INVALID_PARAMETERS:
        case ERROR_PARAM_MISSING_USER_ID: handleSubmissionError(); break;
 
        // unknown state
        case ERROR_UNKNOWN:
        default: handle(); break;
      }
    }
 
    // This list of errors handles missing/incorrect inputs for Form Submissions
    final List<FieldError> fieldErrors = exception.getFieldErrors();
    for (FieldError fieldError : fieldErrors) {
      final SubmissionErrorCode code = fieldError.getErrorCode();
      final String message = fieldError.getMessage();
      final FormField formField = fieldError.getFormField(); // which form field has issues
 
      switch (code) {
        case ERROR_FORM_DUPLICATE:
        case ERROR_FORM_DUPLICATE_NICKNAME:
        case ERROR_FORM_INVALID_EMAILADDRESS:
        case ERROR_FORM_INVALID_IPADDRESS:
        case ERROR_FORM_INVALID_OPTION:
        case ERROR_FORM_PATTERN_MISMATCH:
        case ERROR_FORM_PROFANITY:
        case ERROR_FORM_REJECTED:
        case ERROR_FORM_REQUIRED:
        case ERROR_FORM_REQUIRED_EITHER:
        case ERROR_FORM_REQUIRED_NICKNAME:
        case ERROR_FORM_REQUIRES_TRUE:
        case ERROR_FORM_RESTRICTED:
        case ERROR_FORM_STORAGE_PROVIDER_FAILED:
        case ERROR_FORM_SUBMITTED_NICKNAME:
        case ERROR_FORM_TOO_FEW:
        case ERROR_FORM_TOO_HIGH:
        case ERROR_FORM_TOO_LONG:
        case ERROR_FORM_TOO_LOW:
        case ERROR_FORM_TOO_SHORT:
        case ERROR_FORM_UPLOAD_IO:
        case ERROR_PARAM_DUPLICATE_SUBMISSION:
        case ERROR_PARAM_INVALID_SUBJECT_ID:
        case ERROR_PARAM_MISSING_SUBJECT_ID: handleEachSeparate(); break;
 
        // unknown state
        case ERROR_UNKNOWN:
        default: handle(); break;
      }
    }
  }
});

Generic form field options

These options are available on the builder for all Submission request objects.

requestBuilder
  // User's external ID. Do not use email addresses for this value.
  .userId("userId")
 
  // User nickname display text
  .userNickname("userNickname")
 
  // Value of the encrypted user. This parameter demonstrates that a user has been authenticated.
  .user("user")
 
  // Email address where the submitter will receive the confirmation email. If you are configured to use hosted email authentication, this parameter is required.
  .hostedAuthenticationEmail("[email protected]")
 
  // URL of the link contained in the user authentication email. This should point to a landing page where a web application exists to complete the user authentication process.
  .hostedAuthenticationCallback("yourwebcallback.com")
 
  // Arbitrary text that may be saved alongside content to indicate vehicle by which content was captured.
  .campaignId("campaignId")
 
  // Locale to display Labels, Configuration, Product Attributes and Category Attributes in.
  .locale("en_US")
 
  // User's email address.
  .userEmail("[email protected]")
 
  // User location text.
  .userLocation("Austin, TX")
 
  // Boolean indicating whether or not the user wants to be notified when their content is published.
  .sendEmailAlertWhenPublished(true)
 
  // Boolean indicating whether or not the user agreed to the terms and conditions. Required depending on the your configuration.
  .agreedToTermsAndConditions(true)

Custom key-value pairs

If there is some form parameter that is not supported by the SDK, that your configuration supports, you can add it to a Submission Request as follows,

final ReviewSubmissionRequest reviewSubmitRequest = new ReviewSubmissionRequest.Builder(Action.Submit, productId)  
  .authenticationProvider(authProvider)  
  .addCustomSubmissionParameter("customKey", "customValue")  
  .build();