Sharing Images on Twitter from Phimpme Android App Using twitter4j

As sharing an image to the social media platform is an important feature in Phimpme android. In my previous blog, I have explained how to authenticate the Android application with Twitter. In this blog, I will discuss how to upload an image directly on Twitter from the application after successfully logging to Twitter.

To check if the application is authenticated to Twitter or not.

When the application is successfully authenticated Twitter issues a Token which tells the application if it is connected to Twitter or not. In LoginActivity.java the function isActive returns a boolean value. True if the Twitter token is successfully issued or else false.  

public static boolean isActive(Context ctx) {
        SharedPreferences sharedPrefs = ctx.getSharedPreferences(AppConstant.SHARED_PREF_NAME, Context.MODE_PRIVATE);
        return sharedPrefs.getString(AppConstant.SHARED_PREF_KEY_TOKEN, null) != null;
    }

We call isActive function from LoginActive class to check if the application is authenticated to Twitter or not. We call it before using the share function in sharingActivity:

if (LoginActivity.isActive(context)) {
                try {
                    // Send Image function
} catch (Exception ex) {
                    Toast.makeText(context, "ERROR", Toast.LENGTH_SHORT).show();
 }

We have saved the image in the internal storage of the device and use saveFilePath to use the path of the saved image. In Phimpme we used HelperMethod class where our share function resides, and while the image is being shared an alert dialog box with spinner pops on the screen.

Sending the image to HelperMethod class

First,

We need to get the image and convert it into Bitmaps. Since, the image captured by the phone camera is usually large to upload and it will take a lot of time we need to compress the Bitmap first. BitmapFactory.decodeFile(specify name of the file) is used to fetch the file and convert it into bitmap.

To send the data we used FileOutStream to the set the path of the file or image in this case. Bitmap.compress method is used to compress the image to desired value and format. In Phimpme we are converting it into PNG.  

Bitmap bmp = BitmapFactory.decodeFile(saveFilePath);
                    String filename = Environment.getExternalStorageDirectory().toString() + File.separator + "1.png";
                    Log.d("BITMAP", filename);
                    FileOutputStream out = new FileOutputStream(saveFilePath);
                    bmp.compress(Bitmap.CompressFormat.PNG, 90, out);

                    HelperMethods.postToTwitterWithImage(context, ((Activity) context), saveFilePath, caption, new HelperMethods.TwitterCallback() {

                        @Override
                        public void onFinsihed(Boolean response) {
                            mAlertBuilder.dismiss();
                            Snackbar.make(parent, R.string.tweet_posted_on_twitter, Snackbar.LENGTH_LONG).show();
                        }

Post image function

To post the image on Twitter we will use ConfigurationBuilder class. We will create a new object of the class and then attach Twitter consumer key, consumer secret key, Twitter access token, and twitter token secret.

  • setOAuthConsumerKey() function is used to set the consumer key which is generated by the Twitter when creating the application in the Twitter development environment.
  • Similarly, setOAuthConsumerSecret() function is used to set the consumer secret key.
  • Specify the token key which generated after successfully connecting to twitter in setOAuthAcessToken() fuction and Token secret in setOAuthAcessTokenSecret() function.  
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();       configurationBuilder.setOAuthConsumerKey(context.getResources().getString(R.string.twitter_consumer_key));
configurationBuilder.setOAuthConsumerSecret(context.getResources().getString(R.string.twitter_consumer_secret));
configurationBuilder.setOAuthAccessToken(LoginActivity.getAccessToken((context)));
configurationBuilder.setOAuthAccessTokenSecret(LoginActivity.getAccessTokenSecret(context));
        Configuration configuration = configurationBuilder.build();
final Twitter twitter = new TwitterFactory(configuration).getInstance();

Sending Image to twitter:

  • The image is uploaded to twitter using statusUpdate class specified in Twitter4j API.
  • Pass the image file name in status.setMedia(file).
  • Pass the caption in status.updateStatus(caption).
  • updateStatus is used to finally upload the image with the caption.
final File file = new File(imageUrl);

        new Thread(new Runnable() {
            @Override
            public void run() {
                boolean success = true;
                try {
                    if (file.exists()) {
                        StatusUpdate status = new StatusUpdate(message);
                        status.setMedia(file);
                        twitter.updateStatus(status);
                    }else{
                        Log.d(TAG, "----- Invalid File ----------");
                        success = false;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    success = false;
                }

 Conclusion:                                                                                                                      Using Twitter4j API allows sharing image on Twitter without leaving the  application and opening any additional view.

Github

Resources

Displaying Upcoming Sessions at a Microlocation Open Event Android

When I am attending a session in a room, I don’t get information on what is coming up.”

The issue that the user expressed was that he wanted to know what were the upcoming sessions at a microlocation. While I took up this issue in Open Event Android a few days back, I was thinking of ways about how this can be implemented. The app should be easy-to-use even for non-developers and thus, any new feature shouldn’t be too complex in its implementation. We decided upon doing the following:

  • Adding an “upcoming” option in the options menu of the Location activity.
  • This option’s purpose was to trigger the app to show information about the upcoming session in that microlocation.

Initial changes in LocationActivity.java

First of all, we added a new icon in the options menu of LocationActivity.java. One of the things that we learnt there was to use ifRoom|collapseActionView option for the app:showAsAction  

attribute as frequently as possible. This option ensures that the title in the option’s menu is visible at all times irrespective of the options being visible along with their icons.

So in case, the title is too big and there is very little room for the options to appear individually, then instead of squeezing down the title, the “ifRoom” attribute will collapse the option icons and insert a 3-dotted drop-down option list with all the options appearing in the drop-down.

Something like this:

The icon’s XML element and UI looked something like this:

<item
       android:id="@+id/upcoming_sessions"
       android:icon="@drawable/ic_timeline_white_24dp"
       android:title="@string/upcoming"
       app:showAsAction="ifRoom|collapseActionView"
app:actionViewClass="android.support.v7.widget.Button"/>

About the drawable icon that you see in the screenshot above, it was a tough find. Before I talk about how I came across this icon, I will talk about adding an icon in Android Studio.

How to add an icon in Android Studio?

Adding an item in Android studio means adding a drawable at a basic level. You can find all drawables under the app/src/main/res/drawable folder.

To add a new drawable, right-click on the drawable folder and go to new –>Vector asset. A window similar to what is shown below will appear.

Now, on selecting the “icon” option you will be taken to a huge list of icons that you can add in your app and then use them subsequently. But the problem here is that it is tough at times to find the icon that will be fit for your purpose. Like in my case, there was no direct icon for “upcoming”. This is when we had to do something more. We had to browse to this amazing site by Google: https://material.io/icons/ This site shows all the available icons in a much more interactive way and it was a lot more easier for me to come across the icon we wanted using this site.

The vector drawable file for the icon we chose looks like this:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
       android:width="24dp"
       android:height="24dp"
       android:viewportWidth="24.0"
       android:viewportHeight="24.0">
   <path
       android:fillColor="#FFFFFF"
       android:pathData="M23,8c0,1.1 -0.9,2 -2,2 -0.18,0 -0.35,-0.02 -0.51,-0.07l-3.56,3.55c0.05,0.16 0.07,0.34 0.07,0.52 0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2c0,-0.18 0.02,-0.36 0.07,-0.52l-2.55,-2.55c-0.16,0.05 -0.34,0.07 -0.52,0.07s-0.36,-0.02 -0.52,-0.07l-4.55,4.56c0.05,0.16 0.07,0.33 0.07,0.51 0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2 0.9,-2 2,-2c0.18,0 0.35,0.02 0.51,0.07l4.56,-4.55C8.02,9.36 8,9.18 8,9c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,0.18 -0.02,0.36 -0.07,0.52l2.55,2.55c0.16,-0.05 0.34,-0.07 0.52,-0.07s0.36,0.02 0.52,0.07l3.55,-3.56C19.02,8.35 19,8.18 19,8c0,-1.1 0.9,-2 2,-2s2,0.9 2,2z"/>
</vector>

What would the upcoming icon do?

Keeping in mind the necessity for the feature to be less complex, I decided that the upcoming icon will lead the user to a dialog box that shows the status of upcoming sessions in that micro location. The implementation for this feature involved 2 main things:

  1. Finding out the upcoming session from the list of sessions in the microlocation.
  2. Generate a dialog box that shows information about that session.

Finding position of upcoming session in Recycler View

Upcoming session will be a session whose starting time comes after the current time. So the approach was simple.

  1. Run a loop on a sorted list of all sessions in a microlocation.
  2. Find out every session’s start time.
  3. Compare the start time of every session with the current time.
  4. Find the first session whose start time comes after the current time.
  5. Store that session’s position, name, ID and other stuff like track name and track color.
  6. Break out of the loop.

This was the basic logic or algorithm, so to say. Here’s the implementation in the upcomingSession() function:

public void upcomingSession(){
   String upcomingTitle = "";
   String track = "";
   String color = null;
   Date current = new Date();
   for (Session sess:sortedSessions){
       try {
           Date start = DateUtils.getDate(sess.getStartsAt());
           if (start.after(current)){
               upcomingTitle = sess.getTitle();
               track = sess.getTrack().getName();
               color = sess.getTrack().getColor();
               break;
           }
       } catch (ParseException e) {
           e.printStackTrace();
       }
   }

Now, displaying a dialog box consisting of all the necessary information is an easy thing to do once you have the required information. So, I’ll just provide some code for it here without explaining much about it.

The initialisations:

public void upcomingSessionsInitial(){
   upcomingDialogBox = new Dialog(this);
           upcomingDialogBox.setContentView(R.layout.upcoming_dialogbox);
           trackImageIcon = (ImageView)upcomingDialogBox.findViewById(R.id.track_image_drawable);
           upcomingSessionText = (TextView) upcomingDialogBox.findViewById(R.id.upcoming_session_textview);
           upcomingSessionTitle = (TextView) upcomingDialogBox.findViewById(R.id.upcoming_Session_title);
           Button dialogButton = (Button) upcomingDialogBox.findViewById(R.id.upcoming_button);
           dialogButton.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View view) {
                   upcomingDialogBox.dismiss();
               }
           });
}

The calling:

switch (item.getItemId()){
       case R.id.action_map_location:
           FragmentManager fragmentManager = getSupportFragmentManager();
           FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

           Bundle bundle = new Bundle();
           bundle.putBoolean(ConstantStrings.IS_MAP_FRAGMENT_FROM_MAIN_ACTIVITY, false);
           bundle.putString(ConstantStrings.LOCATION_NAME, location);

           Fragment mapFragment = ((OpenEventApp)getApplication())
                   .getMapModuleFactory()
                   .provideMapModule()
                   .provideMapFragment();
           mapFragment.setArguments(bundle);
           fragmentTransaction.replace(R.id.content_frame_location, mapFragment, FRAGMENT_TAG_LOCATION).addToBackStack(null).commit();

           sessionRecyclerView.setVisibility(View.GONE);
           noSessionsView.setVisibility(View.GONE);
           menu.setGroupVisible(R.id.menu_group_location_activity, false);
           return true;
       case android.R.id.home:
           onBackPressed();
           getSupportFragmentManager().popBackStack();
           sessionRecyclerView.setVisibility(View.VISIBLE);
           return true;
       case R.id.upcoming_sessions:
           upcomingDialogBox.show();
           return true;
       default:
           return true;
   }
}

Final result:

This is the final result or solution that we generated for the issue that was addressed by one of the users:

Some useful links are:

Control flow of SUSI AI on Android and database management using Realm

While developing a chat based android application, one of the most important things is keeping track of user’s messages. Since the user might want to access them in the absence of Internet connectivity (i.e remotely) as well, storing them locally is also important.

In SUSI we are using Realm to keep things organized in a systematic manner and constructing model (or adding appropriate attributes) for every new data type which the application needs. Right now we have three main models namely ChatMessage, WebLink and WebSearchModel. These three java classes define the structure of each possible message.  ChatMessage evaluates and classifies incoming response from server either to be an image or map or pie chart or web search url or other valid types of response. WebSearchModel and WebLink models are there to manage those results which contains link to various web searches.

Various result based lists are maintained for smooth flow of application. Messages sent in absence of Internet are stored in a list – nonDelivered. All the messages have an attribute isDelivered which is set to true if and only if they have been queried, otherwise the attribute is set to false which puts it in the nonDelivered list. Once the phone is connected back to the internet and the app is active in foreground, the messages are sent to server, queried and we get the response back in the app’s database where the attributes are assigned accordingly.

 

I will explain a functionality below that will give a more clear view about our coding practices and work flow.

When a user long taps a message, few options are listed(these actions are defined in recycleradapters->ChatFeedRecyclerAdapter.java) from which you may select one. In the code, this triggers the method onActionsItemClicked(). In this Overridden method, we handle what happens when a user clicks on one of the following options from item menu. In this post I’ll be covering only about the star/important message option.

case R.id.menu_item_important:
    nSelected = getSelectedItems().size();
    if (nSelected >0)
    {
        for (int i = nSelected - 1; i >= 0; i--) 
        {
            markImportant(getSelectedItems().get(i));
        }
        if(nSelected == 1) 
        {
            Toast.makeText(context,nSelected+" message 
            marked 
            important",Toast.LENGTH_SHORT).show();
        } 
        else 
        {
            Toast.makeText(context, nSelected + " 
            messages marked important",                                                      
            Toast.LENGTH_SHORT).show();
        }
        Important = realm.where(ChatMessage.class).
        equalTo("isImportant",true)
        .findAll().sort("id");
        for(int i=0;i<important.size();++i)
            Log.i("message ","" + 
            important.get(i).getContent());
        Log.i("total ",""+important.size());
        actionMode.finish();
    }
return true;

We have the count of messages which were selected. Each message having a unique id is looped through and the attribute “isImportant” of each message object is modified accordingly. To modify this field, We call the method markImportant() and pass the id of message which has to be updated.

public void markImportant(final int position) {
    realm.executeTransaction(new Realm.Transaction() {
        @Override
        public void execute(Realm realm) {
            ChatMessage chatMessage = getItem(position);
            chatMessage.setIsImportant(true);
            realm.copyToRealmOrUpdate(chatMessage);
        }
    });
}

This method copies the instance of the message whose id it has received and updates the attribute “isImportant” and finally updates the message instance in the database.

Below given is the code for ImportantMessage activity which will help you understand properly how lists are used to query the database.

public class ImportantMessages extends AppCompatActivity {
 
    private Realm realm;
    private RecyclerView rvChatImportant;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       realm = Realm.getDefaultInstance();
       rvChatImportant = (RecyclerView) findViewById 
                          (R.id.rv_chat_important);
       actionBar.setDisplayHomeAsUpEnabled(true);
       setChatBackground();
       setupAdapter();
 
        //call to other methods
    }
 
    private void setupAdapter() {
        rvChatImportant = (RecyclerView) findViewById 
                          (R.id.rv_chat_important);
        LinearLayoutManager linearLayoutManager = new 
                            LinearLayoutManager(this);
        linearLayoutManager.setStackFromEnd(true);
        rvChatImportant. 
        setLayoutManager(linearLayoutManager);
        rvChatImportant.setHasFixedSize(false);
        RealmResults<ChatMessage> importantMessages = 
        realm.where(ChatMessage.class). 
        equalTo("isImportant",true).findAll().sort("id");
        TextView tv_msg = (TextView) findViewById 
                          (R.id.tv_empty_list);
 
        if(importantMessages.size()!=0)
            tv_msg.setVisibility(View.INVISIBLE);
        else
            tv_msg.setVisibility(View.VISIBLE);
 
        ChatFeedRecyclerAdapter recyclerAdapter = new 
             ChatFeedRecyclerAdapter(Glide.with(this), this, 
             importantMessages, true);
        rvChatImportant.setAdapter(recyclerAdapter);
        rvChatImportant.addOnLayoutChangeListener(new 
        View.OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View view, int left, 
            int top, int right, int bottom,
            int oldLeft, int oldTop, int oldRight, int 
            oldBottom) {
                if (bottom < oldBottom) {
                    rvChatImportant.postDelayed(new 
                    Runnable() {
                        @Override
                        public void run() {
                            int scrollTo = 
                            rvChatImportant.getA 
                            dapter().getItemCount() - 1;
                            scrollTo = scrollTo >= 0 ? 
                            scrollTo : 0;                             
                            rvChatImportant. 
                            scrollToPosition(scrollTo);
                        }
                    }, 10);
                }
            }
        });
    }
}