Getting started with Docker Compose

In this post, I will talk about running multiple containers at once using Docker Compose.

The problem ?

Suppose you have a complex app with Database containers, Redis and what not. How are you going to start the app ? One way is to write a shell script that starts the containers one by one.

docker run postgres:latest --name mydb -d
docker run redis:3-alpine --name myredis -d
docker run myapp -d

Now suppose these containers have lots of configurations (links, volumes, ports, environment variables) that they need to function. You will have to write those parameters in the shell script.

docker network create myapp_default
docker run postgres:latest --name db -d -p 5432:5432 --net myapp_default
docker run redis:3-alpine --name redis -d -p 6379:6379 \
    --net myapp_default -v redis:/var/lib/redis/data
docker run myapp -d -p 5000:5000 --net myapp_default -e SOMEVAR=value \
    --link db:db --link redis:redis -v storage:/myapp/static

Won’t it get un-manageable ? Won’t it be great if we had a cleaner way to running multiple containers. Here comes docker-compose to the rescue.

Docker compose

Docker compose is a python package which does the job of handling multiple containers for an application very elegantly. The main file of docker-compose is docker-compose.yml which is a YAML like syntax file with the settings/components required to run your app. Once you define that file, you can just do docker-compose up to start your app with all the components and settings. Pretty cool, right ?

So let’s see the docker-compose.yml for the fictional app we have considered above.

version: '2'

services:
  db:
    image: postgres:latest
    ports:
      - '5432:5432'

  redis:
    image: 'redis:3-alpine'
    command: redis-server
    volumes:
      - 'redis:/var/lib/redis/data'
    ports:
      - '6379:6379'

  web:
    build: .
    environment:
      SOMEVAR: value
    links:
      - db:db
      - redis:redis
    volumes:
      - 'storage:/myapp/static'
    ports:
      - '5000:5000'

volumes:
  redis:
  storage:

Once this file is in the project’s root directory, you can use docker-compose up to start the application. It will run the services in the order in which they have been defined in the YAML file.

Docker compose has a lot of commands that generally correspond to the parameters that docker runaccepts. You can see a full list on the official docker-compose reference.

Conclusion

It’s no doubt that docker-compose is a boon when you have to run complex applications. It personally use Compose in every dockerized application that I write. In GSoC 16, I dockerized Open Event. Here is the docker-compose.yml file if you are interested.

PS – If you liked this post, you might find my other posts on Docker interesting. Do take a look and let me know your views.

 

{{ Repost from my personal blog http://aviaryan.in/blog/gsoc/docker-compose-starting.html }}

Continue ReadingGetting started with Docker Compose

KISS Datatable

Recenlty I’ve faced a problem with sorting columns in Datatable.

What is Datatable?

Datatable is a plug-in for Jquery library. It provides a lot of features like pagination, quick search or multi-column ordering. Besides, you can develop easily Bootstrap or Foundation ui css styles. There are also more other option but It doesn’t make sense to list it here, because you can visit their site and you can read clearly documentation. On Datatable website you can see a lot of examples. First of them shows how to improve your ordinary table to awesome and rich of features table. One function changes everything, It’s fantastic!  

$('#myTable').DataTable();

Returning to my problem which I’ve faced, as I told it was problem related to sorting column in table.

I know sorting is a trivial thing. I hope that everyone knows it 🙂 Sorting by a date is also implemented in a datatable library. So everything is clear when we don’t change date format to human readable format. I mean something like this ‘3 hours ago’, ‘1 year ago’.

When Open Event team tested how datatable manages ordering columns in that format it didn’t work. It’s quite hard to sort by that format, So I’ve invented an idea. Surely you are wondering what i’ve invented. I’ve postponed my minds about sort by this values. It can direct to overwork. When I thought about it, weird ideas came to my mind, a lots of conditions, If statements… Therefore I’ve resigned from this. I’ve used KISS principle. KISS means ‘keep it simple stupid’. I like it!

Therefore that sorting is implemented on frontend side. I’ve decided not to display human readable date format at the beginning. I find  all dates which have format “YYYY-MM-DD HH:mm” then I replace that format to human readable format. So it’s very quick and comfortable, and doesn’t require a lot conditions to write. Of course I’ve tried to implement it in Datatable library. I suppose that it would  require more effort than it’s now.

Below You can see great function which changes a date in frontend side but does not change data in a datatable. So sorting process takes place in a datatable using format  “YYYY-MM-DD HH:mm” but user see human readable format. Isn’t it awesome?!

function change_column_time_to_humanize_format(datatable_name, column_id) {
  $(datatable_name).each(function( key, value ) {
    $(value).children().each(function( key1, value2 ) {
       if(key1 === column_id ){
          var current_value = $(value2).text().slice(0, 16);
          var changed_value = moment(current_value, "YYYY-MM-DD hh:mm").fromNow()
          var isValid = moment(current_value, "YYYY-MM-DD HH:mm", true).isValid()
          if (changed_value !== current_value && isValid === true){
              $(value2).text(changed_value)
          }
      }
  });
});

 

Continue ReadingKISS Datatable

Twitter Section Using loklak webtweets

In Open event web app, the user can provide URL of social links such as Twitter, Facebook etc in the event.json file inside the ZIP. The previous functionality was to use Twitter API and to generate a timeline showing the tweets of the twitter URL mentioned in event.json by user. But, it can be done by following another approach which reduces the third party dependency i.e Loklak-webtweets.

I have implemented the twitter section using loklak webtweets which can be done very easily.

Step 1:  Including necessary files from loklakwebtweets repository inside index.html. You can find them in js/ folder of this repository.

<script src="./dependencies/jquery.min.js"></script>
<script src="./dependencies/bootstrap.min.js" type="text/javascript"></script>
<script src="./dependencies/loklak-fetcher.js" type="text/javascript"></script>
 <script src="./dependencies/tweets.js" type="text/javascript"></script>

 

Step 2:  Specify the data source in HTML from which twitter data will be fetched. Here I have extracted the last word from the twitter URL provided by the user and passed it to HTML.

const sociallinks = Array.from(event.social_links);
 var twitter ="";
 sociallinks.forEach((link) => {
  if(link.name.toLowerCase() === "twitter") {
   twitter = link.link;
  }
 }) 
 const arrayTwitterLink = sociallink.split('/');
 const twitterLink = arrayTwitterLink[arrayTwitterLink.length - 1];
 
 const urls= {
   twitterLink: twitterLink,
   tweetUrl: twitter,
 };

This code will search twitter link in social links array present in event.json and get its last character which will be provided to data-from and data-query attribute of HTML.

 <section class="sponsorscont">
  <div class="tweet-row">
   <div class="col-sm-12 col-md-12 col-xs-12">
    <i class ="social_twitter fa fa-twitter"></i>
     <div class="tweets-feed" id="tweets" data-count=50 data-query="    {{{eventurls.twitterLink}}}" data-from="{{{eventurls.twitterLink}}}">
     <div class="arrow-up"></div>
      <p id="tweet" class="tweet">
       Loading...
     </p>
   <span style="margin-bottom: 20px;" id="dateTweeted"></span>
    <p>Follow<u>
    <b><a href="{{eventurls.tweetUrl}}"/>
     @{{eventurls.twitterLink}}</a>
    </b></u> for more updates</p> 
     </div> 
    </div>
  </div>
</section>

Step 3 : Now we just need to add styling so that it looks decent. For that, I have written some SASS.

.tweets-feed {
   color: $black;
   line-height: 30px;
   font-size: 20px;
   transition: opacity 0.2s linear;
   margin-bottom: 20px;
   height: 100px;
 
  a {
   color: $black;
   text-decoration: underline;
   font-weight: 700;
  } 

  #dateTweeted {
   font-size: 15px;
   display: block;
  }

}

.tweet-row {
   padding: 0 80px;
   margin-bottom: 80px;
   .social_twitter {
     font-size: 60px;
     margin-bottom: 12px;
  }
}

The output from the above code is a well designed Twitter section fetching tweets from the URL provided as a string in event.json by user.

tweet

 

Continue ReadingTwitter Section Using loklak webtweets

Building the Scheduler UI

{ Repost from my personal blog @ https://blog.codezero.xyz/building-the-scheduler-ui }

If you hadn’t already noticed, Open Event has got a shiny new feature. A graphical and an Interactive scheduler to organize sessions into their respective rooms and timings.

As you can see in the above screenshot, we have a timeline on the left. And a lot of session boxes to it’s right. All the boxes are re-sizable and drag-drop-able. The columns represent the different rooms (a.k.a micro-locations). The sessions can be dropped into their respective rooms. Above the timeline, is a toolbar that controls the date. The timeline can be changed for each date by clicking on the respective date button.

The Clear overlaps button would automatically check the timeline and remove any sessions that are overlapping each other. The Removed sessions will be moved to the unscheduled sessions pane at the left.

The Add new micro-location button can be used to instantly add a new room. A modal dialog would open and the micro-location will be instantly added to the timeline once saved.

The Export as iCal allows the organizer to export all the sessions of that event in the popular iCalendar format which can then be imported into various calendar applications.

The Export as PNG saves the entire timeline as a PNG image file. Which can then be printed by the organizers or circulated via other means if necessary.

Core Dependencies

The scheduler makes use of some javascript libraries for the implementation of most of the core functionality

  • Interact.js – For drag-and-drop and resizing
  • Lodash – For array/object manipulations and object cloning
  • jQuery – For DOM Manipulation
  • Moment.js – For date time parsing and calculation
  • Swagger JS – For communicating with our API that is documented according to the swagger specs.
Retrieving data via the API

The swagger js client is used to obtain the sessions data using the API. The client is asynchronously initialized on page load. The client can be accessed from anywhere using the javascript function initializeSwaggerClient.

The swagger initialization function accepts a callback which is called if the client is initialized. If the client is not initialized, the callback is called after that.

var swaggerConfigUrl = window.location.protocol + "//" + window.location.host + "/api/v2/swagger.json";  
window.swagger_loaded = false;  
function initializeSwaggerClient(callback) {  
    if (!window.swagger_loaded) {
        window.api = new SwaggerClient({
            url: swaggerConfigUrl,
            success: function () {
                window.swagger_loaded = true;
                if (callback) {
                    callback();
                }

            }
        });
    } else {
        if (callback) {
            callback();
        }
    }
}

For getting all the sessions of an event, we can do,

initializeSwaggerClient(function () {  
    api.sessions.get_session_list({event_id: eventId}, function (sessionData) {
        var sessions = sessionData.obj;  // Here we have an array of session objects
    });
});

In a similar fashion, all the micro-locations of an event can also be loaded.

Processing the sessions and micro-locations

Each session object is looped through, it’s start time and end time are parsed into moment objects, duration is calculated, and it’s distance from the top in the timeline is calculated in pixels. The new object with additional information, is stored in an in-memory data store, with the date of the event as key, for use in the timeline.

The time configuration is specified in a separate time object.

var time = {  
    start: {
        hours: 0,
        minutes: 0
    },
    end: {
        hours: 23,
        minutes: 59
    },
    unit: {
        minutes: 15,
        pixels: 48,
        count: 0
    },
    format: "YYYY-MM-DD HH:mm:ss"
};

The smallest unit of measurement is 15 minutes and 48px === 15 minutes in the timeline.

Each day of the event is stored in a separate array in the form of Do MMMM YYYY(eg. 2nd May 2013).

The array of micro-location objects is sorted alphabetically by the room name.

Displaying sessions and micro-locations on the timeline

According to the day selected, the sessions for that day are displayed on the timeline. Based on their time, the distance of the session div from the top of the timeline is calculated in pixels and the session box is positioned absolutely. The height of the session in pixels is calculated from it’s duration and set.

For pixels-minutes conversion, the following are used.

/**
 * Convert minutes to pixels based on the time unit configuration
 * @param {number} minutes The minutes that need to be converted to pixels
 * @returns {number} The pixels
 */
function minutesToPixels(minutes) {  
    minutes = Math.abs(minutes);
    return (minutes / time.unit.minutes) * time.unit.pixels;
}

/**
 * Convert pixels to minutes based on the time unit configuration
 * @param {number} pixels The pixels that need to be converted to minutes
 * @returns {number} The minutes
 */
function pixelsToMinutes(pixels) {  
    pixels = Math.abs(pixels);
    return (pixels / time.unit.pixels) * time.unit.minutes;
}
Adding interactivity to the session elements

Interact.js is used to provide interactive capabilities such as drag-drop and resizing.

To know how to use Interact.js, you can checkout some previous blog posts on the same, Interact.js + drag-drop and Interact.js + resizing.

Updating the session information in database on every change

We have to update the session information in database whenever it is moved or resized. Every time a session is moved or resized, a jQuery event is triggered on $(document) along with the session object as the payload.

We listen to this event, and make an API request with the new session object to update the session information in the database.


The scheduler UI is more complex than said in this blog post. To know more about it, you can checkout the scheduler’s javascript code atapp/static/js/admin/event/scheduler.js.

Continue ReadingBuilding the Scheduler UI

R14 – Memory Quota Exceeded

We, like many other organisations, are using heroku as the deployment server for our project open event organizer server. Things are pretty simple and awesome when your project is in its beginning phase and things run pretty smoothly. But as your project grows, there comes some server problem. And one of the biggest problems as your project grows is memory. Now since various packages have a different amount of memory assigned to you in case of hosting in generic servers such as heroku, so it might result in memory quota exceeded. Recently, we faced such a problem. R14 – Memory Quota Exceeded. Took us quite some time to understand what and why and how this occurred. So let me share a few things I found about this error.

Continue ReadingR14 – Memory Quota Exceeded

Python code examples

I’ve met many weird examples of  behaviour in python language while working on Open Event project. Today I’d like to share some examples with you. I think this knowledge is necessary, if you’d like to increase a  bit your knowledge in python area.

Simple adding one element to python list:

def foo(value, x=[]):
  x.append(value)
  return x

>>> print(foo(1))
>>> print(foo(2))
>>> print(foo(3, []))
>>> print(foo(4))

OUTPUT

[1] 
[1, 2] 
[3]
[1, 2, 4]

First output is obvious, but second not exactly. Let me explain it, It happens because x(empty list) argument is only evaluated once, So on every call foo(), we modify that list, appending a value to it. Finally we have [1,2, 4] output. I recommend to avoid mutable params as default.

Another example:

Do you know which type it is?

>>> print(type([ el for el in range(10)]))
>>> print(type({ el for el in range(10)}))
>>> print(type(( el for el in range(10))))

Again first and second type are obvious <class ‘list’>, <class ‘set’>. You may  think that last one should return type tuple but it returns a generator <class ‘generator’>.

Example:

Do you think that below code returns an exception?

list= [1,2,3,4,5]
>>> print(list [8:])

If you think that above expression returns index error you’re wrong. It returns empty list [].

Example funny boolean operators

>>> 'c' == ('c' or 'b')
True
>>> 'd' == ('a' or 'd')
False
>>> 'c' == ('c' and 'b')
False 
>>> 'd' == ('a' and 'd')
True

You can think that that OR and AND operators are broken.

You have to know how python interpreter behaves while looking for OR and AND operators.

So OR Expression takes the first statement and checks if it is true. If the first statement is true, then Python returns object’s value without checking second value. If first statement is false interpreter checks second value and returns that value.

AND operator checks if first statement is false, the whole statement has to be false. So it returns first value, but if first statement is true it checks second statement and returns second value.

Below i will show you how it works

>>> 'c' == ('c' or 'b')
>>> 'c' == 'c'
True
>>> 'd' == ('a' or 'd')
>>> 'd' == 'a'
False
>>> 'c' == ('c' and 'b')
>>> 'c' == 'b'
False 
>>> 'd' == ('a' and 'd')
>>> 'd' == 'd'
True

I hope that i have explained you how the python interpreter checks OR and AND operators. So know above examples should be more understandable.

Continue ReadingPython code examples

Using ftp-deploy in node.js to publish websites over FTP

In the Open Event Webapp Generator, we recently added the functionality for organisers to submit their ftp credentials and when the website is generated, it’ll automatically upload the website to the chosen ftp server (allowing creation of subdirectory internally, if the organiser so wants).

To achieve we used the very useful nodejs module ftp-deploy which is a wrapper on the popular jsftp library

The code dealing with ftp deployment in our webapp generator can be found here  –

https://github.com/fossasia/open-event-webapp/blob/development/src/backend/ftpdeploy.js

As can be seen, deploying using ftp-deploy is pretty straightforward. Primarily we need a config object

 


  var config = {
    username: ftpDetails.user, //prompted on commandline if not given
    password: ftpDetails.pass, // optional, prompted if none given
    host: ftpDetails.host,
    port: 21,
    localRoot: path.join(__dirname, '/../../dist', appFolder), //local folder containing website
    remoteRoot: ftpDetails.path, //path on ftp server to host website
    exclude: ['.git', '.idea', 'tmp/*'],
    continueOnError: true
};

You can set up some event listeners for events like uploaded uploading and upload-error

Continue ReadingUsing ftp-deploy in node.js to publish websites over FTP

Small Docker images using Alpine Linux

Everyone likes optimization, small file sizes and such.. Won’t it be great if you are able to reduce your Docker image sizes by a factor of 2 or more. Say hello to Alpine Linux. It is a minimal Linux distro weighing just 5 MBs. It also has basic linux tools and a nice package manager APK. APK is quite stable and has a considerable amount of packages.

apk add python gcc

In this post, my main motto is how to squeeze the best out of AlpineLinux to create the smallest possible Docker image. So let’s start.

Step 1: Use AlpineLinux based images

Ok, I know that’s obvious but just for the sake of completeness of this article, I will state that prefer using Alpine based images wherever possible. Python and Redis have their official Alpine based images whereas NodeJS has good unoffical Alpine-based images. Same goes for Postgres, Ruby and other popular environments.

Step 2: Install only needed dependencies

Prefer installing select dependencies over installing a package that contains lots of them. For example, prefer installing gcc and development libraries over buildpacks. You can find listing of Alpine packages on their website.

Pro Tip – A great list of Debian v/s Alpine development packages is at alpine-buildpack-deps Docker Hub page (scroll down to Packages). It is a very complete list and you will always find the dependency you are looking for.

Step 3: Delete build dependencies after use

Build dependencies are required by components/libraries to build native extensions for the platform. Once the build is done, they are not needed. So you should delete the build-dependencies after their job is complete. Have a look at the following snippet.

RUN apk add --virtual build-dependencies gcc python-dev linux-headers musl-dev postgresql-dev \
    && pip install -r requirements.txt \
    && apk del build-dependencies

I am using --virtual to give a label to the pacakages installed on that instance and then when pip install is done, I am deleting them.

Step 4: Remove cache

Cache can take up lots of un-needed space. So always run apk add with --no-cache parameter.

RUN apk add --no-cache package1 package2

If you are using npm for manaing project dependencies and bower for managing frontend dependencies, it is recommended to clear their cache too.

RUN npm cache clean && bower cache clean

Step 5: Learn from the experts

Each and every image on Docker Hub is open source, meaning that it’s Dockerfile is freely available. Since the official images are made as efficient as possible, it’s easy to find great tricks on how to achieve optimum performance and compact size in them. So when viewing an image on DockerHub, don’t forget to peek into its Dockerfile, it helps more than you can imagine.

Conclusion

That’s all I have for now. I will keep you updated on new tips if I find any. In my personal experience, I found AlpineLinux to be worth using. I tried deploying Open Event Server on Alpine but faced some issues so ended up creating a Dockerfile using debain:jessie. But for small projects, I would recommend Alpine. On large and complex projects however, you may face issues with Alpine at times. That maybe due to lack of packages, lack of library support or some other thing. But it’s not impossible to overcome those issues so if you try hard enough, you can get your app running on Alpine.

 

{{ Repost from my personal blog http://aviaryan.in/blog/gsoc/docker-using-alpine.html }}

Continue ReadingSmall Docker images using Alpine Linux

Bottoms sheets in android

Hey Guys I recently used Bottom sheets, so that I should write a blog about it because I don’t see a lot of developers using this in their app UI’s.

It’s a very interesting UI element. A Bottom Sheet is a sheet of material that slides up from the bottom edge of the screen. Displayed only as a result of a user-initiated action, and can be swiped up to reveal additional content. It can be a temporary modal surface or a persistent structural element of an app.

This component was introduced in the Android Design Support library 23.2. Many apps like Google Maps use the bottom sheet, in which a sliding window pops up from the bottom of the screen. Also the Google play music app uses. When we drag up the sheet we see the song details as well as the current playlist.

Usage of expanded and collapsed Bottom sheets in Android

There are 3 states of Bottom sheets :-

  • Expanded — When the sheet is completely visible.
  • Collapsed — When the sheet is partially visible.
  • Hidden — When the sheet is completely hidden.

Step 1 is we need to import the latest design support library. Put this line in your app level build.gradle file.

compile ‘com.android.support:design:23.2.0’

Then one should create a new Blank Activity (not Empty Activity) in Android Studio. It sets up the CoordinatorLayout by default.

So now there ate two layouts created by default namely activity_main.xml and content_main.xml.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="yet.best.bottomsheets.MainActivity"
tools:showIn="@layout/activity_main">

<Button
android:id="@+id/open"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_margin="5dp"
android:text="Open Bottom Sheet" />

<Button
android:id="@+id/collapse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/open"
android:layout_centerHorizontal="true"
android:layout_margin="5dp"
android:text="Collapse Bottom Sheet" />

<Button
android:id="@+id/hide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/collapse"
android:layout_centerHorizontal="true"
android:layout_margin="5dp"
android:text="Hide Bottom Sheet" />

</RelativeLayout>

Notice that 3 Buttons have been created in this layout to perform different actions with the bottom sheets.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="yet.best.bottomsheets.MainActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_main" />

<include
android:id="@+id/bottom_sheet"
layout="@layout/bottomsheet_main" />

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email"
app:layout_anchor="@id/bottom_sheet"
app:layout_anchorGravity="top|right|end" />

</android.support.design.widget.CoordinatorLayout>

Those who aren’t familiar with the coordinator layout — basically there is a base level layout activity_main which contains the FloatingButton and within this layout including content_main.xml which will contain the rest of the layout. Notice that one also has to include bottomsheet_main.xml. This layout contains our bottom sheet layout which will be created next.

Create a new layout file called bottomsheet_main.xml

bottomsheet_main.xml

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="#d3d3d3"
app:behavior_hideable="true"
app:behavior_peekHeight="70dp"
app:layout_behavior="@string/bottom_sheet_behavior">

<TextView
android:id="@+id/heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:text="Collapsed"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Main Content"
android:textSize="20sp" />

</RelativeLayout>

This layout is how our bottom sheet will actually look. You can design it as you want.

Now for the actual java code. This is the easiest part actually. Just set listeners to the 3 buttons created and perform the corresponding action with the bottom sheet.

public class MainActivity extends AppCompatActivity {

BottomSheetBehavior bottomSheetBehavior;
Button open, collapse, hide;
TextView heading;
@Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
  setSupportActionBar(toolbar);

View bottomSheet = findViewById(R.id.bottom_sheet);
  bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);

setup();
 }

private void setup() {
  open = (Button) findViewById(R.id.open);
  collapse = (Button) findViewById(R.id.collapse);
  hide = (Button) findViewById(R.id.hide);
  heading = (TextView) findViewById(R.id.heading);

//Handling movement of bottom sheets from buttons
  open.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
    heading.setText("Welcome");
    heading.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.colorPrimary));
   }
  });

collapse.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
    heading.setText("Collapsed");
    heading.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.colorAccent));
   }
  });

hide.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
   }
  });

//Handling movement of bottom sheets from sliding
  bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
   @Override
   public void onStateChanged(View bottomSheet, int newState) {
    if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
     heading.setText("Collapsed");
     heading.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.colorAccent));
    } else if (newState == BottomSheetBehavior.STATE_EXPANDED) {
     heading.setText("Welcome");
     heading.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.colorPrimary));
    }
   }

@Override
   public void onSlide(View bottomSheet, float slideOffset) {}
  });
 }
}

We just use the bottomSheetBehavior.setState() method to set the relevant state on each button click.

The bottom sheets can also be dragged by touch gestures. One can simply touch the sheet and drag them up or down. For these touch gestures to be handled one has to implement the onStateChanged() listener. This listener is fired everytime the state of the sheet changes by gestures. Whenever this triggers it checks the state of the bottom sheet and again do the desired action which user would have done by the button clicks.

As you can see, this is a pretty neat UI solution and can be implemented so easily. Go try it out for yourself. Adios!

Continue ReadingBottoms sheets in android

Implementing Fee Structure for Ticketing

After implementing the ticketing system in Open Event, it was necessary to implement the control of fees for each type of currency. Thus an Admin page for controlling the percentage of fees and the maximum allowed fee for each type of currency was made:

1

Here initially on loading the system for the first time the service fees and maximum fees are 0. The Super Admin then sets the values. However if for some reason the maximum fee is still left blank then it becomes equal to the service fees.

2

The backend code for this is as follows:

if 'service_fee' in kwargs:
    ticket_service_fees = kwargs.get('service_fee')
    ticket_maximum_fees = kwargs.get('maximum_fee')
    from app.helpers.data_getter import DataGetter
    from app.helpers.data import save_to_db
    currencies = DataGetter.get_payment_currencies()
    ticket_fees = DataGetter.get_fee_settings()
    if not ticket_fees:
        for i, (currency, has_paypal, has_stripe) in enumerate(currencies):
            currency = currency.split(' ')[0]
            if float(ticket_maximum_fees[i]) == 0.0:
                ticket_maximum_fees[i] = ticket_service_fees[i]
            ticket_fee = TicketFees(currency=currency,
                                    service_fee=ticket_service_fees[i],
                                    maximum_fee=ticket_maximum_fees[i])
            save_to_db(ticket_fee, "Ticket Fees settings saved")
    else:
        i = 0
        for fee in ticket_fees:
            if float(ticket_maximum_fees[i]) == 0.0:
                ticket_maximum_fees[i] = ticket_service_fees[i]
            fee.service_fee = ticket_service_fees[i]
            fee.maximum_fee = ticket_maximum_fees[i]
            save_to_db(fee, "Fee Options Updated")
            i += 1

So it checks if the ‘service fees’ is there in the request. If Yes then it stores all the service fees in one list and all the corresponding maximum fees in another list. Then it checks if the fee settings are already stored in the database using the

ticket_fees = DataGetter.get_fee_settings()

If the organizer is setting the fees for the first time then it will return None. In that case new settings are created. If fees are already created then the settings are just modified. And in both cases if the maximum fee is 0 then the maximum fee will be set to service fee.

Thus the fee system is implemented for the tickets.

Continue ReadingImplementing Fee Structure for Ticketing