본문 바로가기

안드로이드/러닝패스

[#A5 DEPRECATED] SNS 같은 FEED를 만드는 방법

유명한 SNS 앱에는 모두 각자의 FEED를 가지고 있습니다. 이번 포스트에서는 VOLLEY 라이브러리를 사용해 앱에서 FEED를 구현해보겠습니다.


명시되지 않은 모든 소스코드 출처 : www.androidhive.info

 

기본적인 구조는 다음과 같이 구성됩니다.


Activity 측면


1
2
3
4
5
6
7
8
//ADAPTER
FeedListAdapter.java
 
//MAIN
MainAcitivy.java
 
//POJO
FeedItem.java 
cs


Layout 측면


1
2
3
4
5
//MAIN
activity_main.xml

//FEED COMPONETS
feed_item.xml
cs

 

이외


1
2
3
4
5
6
7
8
//FOR FEED PICTURE
LruBitmapCache.java
 
//FOR VOLLEY
AppController.java

//FOR FEED IMAGE
FeedImageView.java
cs

 

기본적인 동작은 다음과 같습니다.


1. MainActivity에서 Feed 데이터를 불러옴


2. 불러온 Feed 데이터를 FeedItem에 저장한 후, FeedListAdapter에 전달


3. FeedListAdapter에서 feed_item (List에 표시될 피드 객체)의 컴포넌트를 초기화 시키고 데이터를 SET


4. activity_main 에서 ListView를 통해 FeedListAdapter 연결

 

첫째로 AppController.java, LruBitmapCahche.java, FeedImageView.java, feed_item.xml을 각각 프로젝트에 추가합니다.


AppController


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class AppController extends Application {
 
    public static final String TAG = AppController.class.getSimpleName();
 
    private RequestQueue mRequestQueue;
    private ImageLoader mImageLoader;
    LruBitmapCache mLruBitmapCache;
 
    private static AppController mInstance;
 
    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
    }
 
    public static synchronized AppController getInstance() {
        return mInstance;
    }
 
    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        }
 
        return mRequestQueue;
    }
 
    public ImageLoader getImageLoader() {
        getRequestQueue();
        if (mImageLoader == null) {
            getLruBitmapCache();
            mImageLoader = new ImageLoader(this.mRequestQueue, mLruBitmapCache);
        }
 
        return this.mImageLoader;
    }
 
    public LruBitmapCache getLruBitmapCache() {
        if (mLruBitmapCache == null)
            mLruBitmapCache = new LruBitmapCache();
        return this.mLruBitmapCache;
    }
 
    public <T> void addToRequestQueue(Request<T> req, String tag) {
        req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
        getRequestQueue().add(req);
    }
 
    public <T> void addToRequestQueue(Request<T> req) {
        req.setTag(TAG);
        getRequestQueue().add(req);
    }
 
    public void cancelPendingRequests(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
}
cs


추가 후 반드시 다음과 같이 AndroidMenifest에 추가합니다.


1
2
3
4
<uses-permission android:name="android.permission.INTERNET"/>
 
<application
        android:name="com.myPackage.AppController">
cs

 

LruBitmapCache.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class LruBitmapCache extends LruCache<String, Bitmap> implements
        ImageCache {
    public static int getDefaultLruCacheSize() {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        final int cacheSize = maxMemory / 8;
 
        return cacheSize;
    }
 
    public LruBitmapCache() {
        this(getDefaultLruCacheSize());
    }
 
    public LruBitmapCache(int sizeInKiloBytes) {
        super(sizeInKiloBytes);
    }
 
    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight() / 1024;
    }
 
    @Override
    public Bitmap getBitmap(String url) {
        return get(url);
    }
 
    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        put(url, bitmap);
    }
}
}
cs

 

FeedImageView.java feed_item.xml링크에서 확인하세요.

 

완료했다면 이제 FeedListAdapter.java, MainActivity.java, activity_main.xml를 추가합니다.


FeedListAdapter.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
public class FeedListAdapter extends BaseAdapter {  
    private Activity activity;
    private LayoutInflater inflater;
    private List<FeedItem> feedItems;
    ImageLoader imageLoader = AppController.getInstance().getImageLoader();
 
    public FeedListAdapter(Activity activity, List<FeedItem> feedItems) {
        this.activity = activity;
        this.feedItems = feedItems;
    }
 
    @Override
    public int getCount() {
        return feedItems.size();
    }
 
    @Override
    public Object getItem(int location) {
        return feedItems.get(location);
    }
 
    @Override
    public long getItemId(int position) {
        return position;
    }
 
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
 
        if (inflater == null)
            inflater = (LayoutInflater) activity
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (convertView == null)
            convertView = inflater.inflate(R.layout.feed_item, null);
 
        if (imageLoader == null)
            imageLoader = AppController.getInstance().getImageLoader();
 
        //Componets initializing
        TextView name = (TextView) convertView
                .findViewById(R.id.name);
        TextView timestamp = (TextView) convertView
                .findViewById(R.id.timestamp);
        TextView statusMsg = (TextView) convertView
                .findViewById(R.id.txtStatusMsg);
        TextView url = (TextView) convertView
                .findViewById(R.id.txtUrl);
        NetworkImageView profilePic = (NetworkImageView) convertView
                .findViewById(R.id.profilePic);
        FeedImageView feedImageView = (FeedImageView) convertView
                .findViewById(R.id.feedImage1);
        //end
 
        FeedItem item = feedItems.get(position);
 
        name.setText(item.getName());
 
        // Converting timestamp into x ago format
        CharSequence timeAgo = DateUtils.getRelativeTimeSpanString(
                Long.parseLong(item.getTimeStamp()),
                System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS);
        timestamp.setText(timeAgo);
 
        // Chcek for empty status message
        if (!TextUtils.isEmpty(item.getStatus())) {
            statusMsg.setText(item.getStatus());
            statusMsg.setVisibility(View.VISIBLE);
        } else {
            // status is empty, remove from view
            statusMsg.setVisibility(View.GONE);
        }
 
        // Checking for null feed url
        if (item.getUrl() != null) {
            url.setText(Html.fromHtml("<a href=\"" + item.getUrl() + "\">"
                    + item.getUrl() + "</a> "));
 
            // Making url clickable
            url.setMovementMethod(LinkMovementMethod.getInstance());
            url.setVisibility(View.VISIBLE);
        } else {
            // url is null, remove from the view
            url.setVisibility(View.GONE);
        }
 
        // user profile pic
        profilePic.setImageUrl(item.getProfilePic(), imageLoader);
 
        // Feed image
        if (item.getImge() != null) {
            feedImageView.setImageUrl(item.getImge(), imageLoader);
            feedImageView.setVisibility(View.VISIBLE);
            feedImageView
                    .setResponseObserver(new FeedImageView.ResponseObserver() {
                        @Override
                        public void onError() {
                        }
 
                        @Override
                        public void onSuccess() {
                        }
                    });
        } else {
            feedImageView.setVisibility(View.GONE);
        }
 
        return convertView;
    }
 
}
cs

 

MainAcitivity.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//Writed By www.androidhive.info @ravi
//Modified By Trendy Develope

public class MainActivity extends Activity {

    private static final String TAG = MainActivity.class.getSimpleName();
    private ListView listView;
    private FeedListAdapter listAdapter;
    private List<FeedItem> feedItems;

private int offSet = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        swipeRefreshLayout = (SwipyRefreshLayout) findViewById
                (R.id.swipy_refresh_layout_feed);
        listView = (ListView) findViewById(R.id.list);
 
        feedItems = new ArrayList<FeedItem>();
 
        listAdapter = new FeedListAdapter(this, feedItems);
        listView.setAdapter(listAdapter);
         
        // These two lines not needed,
        // just to get the look of face**** sns (changing background color & hiding the icon)
        getActionBar().setBackgroundDrawable(new ColorDrawable(Color.parseColor("#3b5998")));
        getActionBar().setIcon(
                   new ColorDrawable(getResources().getColor(android.R.color.transparent)));
 
        swipeRefreshLayout.setColorSchemeResources
                (R.color.feed_bg, R.color.feed_bg);

        swipeRefreshLayout.setOnRefreshListener(new SwipyRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh(SwipyRefreshLayoutDirection direction) {
                catchPosts();
            }
        });

//start load feed data
        swipeRefreshLayout.post(new Runnable() {
            @Override
            public void run() {
                swipeRefreshLayout.setRefreshing(true);
                catchPosts();
            }
        });

    }

private void catchPosts() {

        swipeRefreshLayout.setRefreshing(true);

        // appending offset to url
        String url = "my_feed_url" + offset;

        JsonArrayRequest req = new JsonArrayRequest(url,
                new Response.Listener<JSONArray>() {
                    @Override
                    public void onResponse(JSONArray response) {
                        if (response.length() > 0) {

                            //JSONArray feedArray = response.getJSONArray("feed");

                            for (int i = 0; i < response.length(); i++) {
                                try {

                                    //JSONObject feedObj = (JSONObject) feedArray.get(i);
                                    JSONObject feedObj = response.getJSONObject(i);

                                    String id = feedObj.getString("id");
                                    String name = URLDecoder.decode
                                            (URLEncoder.encode(feedObj.getString("name"), 
"iso8859-1"), "UTF-8");
                                    String image= feedObj.getString("image");
                                    String status = URLDecoder.decode
                                            (URLEncoder.encode(feedObj.getString("status"), 
"iso8859-1"), "UTF-8");
                                    String profilePic = feedObj.getString("profilePic");
                                    String timestamp = feedObj.getString("timestamp");

                                    FeedItem item = new FeedItem(id, name, image, status,
profilePic, timestamp);
                                    feedItems.add(listAdapter.getCount(), item);

                                    offset++;

                                } catch (Exception e) {
                                    Log.e("TAG""JSON Parsing error: " + e.getMessage());
                                }
                            }

                            listAdapter.notifyDataSetChanged();

                        }else{

                            if(offset == 0){
                                Toast.makeText(getApplicationContext(), "준비된 피드가 없습니다"
Toast.LENGTH_LONG).show();
                            }else {
                                Toast.makeText(getApplicationContext(), "더 이상 표시할 게시물이 없습니다"
Toast.LENGTH_LONG).show();
                            }

                        }

                        swipeRefreshLayout.setRefreshing(false);

                    }//OnResponse End
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {

                Log.e("TAG""Server Error: " + error.getMessage());
                // stopping swipe refresh
                swipeRefreshLayout.setRefreshing(false);

            }
        });

        AppController.getInstance().addToRequestQueue(req);

    }
 
}
cs


activity_main.xml


이것을 추가하기 전에 SwipyRefreshLayout 라이브러리를 앱에 추가합니다. 라이브러리에 대한 설명은 링크을 참고하세요.


1
implementation 'com.github.orangegangsters:swipy:1.2.3@aar'
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">
 
    <com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/swipy_refresh_layout_feed"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        app:direction="bottom">
 
        <ListView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/list"
            android:layout_alignTop="@+id/view"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true" />
 
    </com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout>
 
 
</RelativeLayout>
cs

 

이제 data를 출력하는 php파일을 작성할 차례입니다. 관련 강의는 포스트 [#G1 : PHP를 이용해 DB[MySQL]에 데이터를 저장하는 방법]]과 [#G2 : PHP를 이용해 DB [MySQL]의 데이터를 내보내는 방법]를 참고하세요.


아래의 코드는 작성 후 검증되지 않았습니다. 따라서 발생할 수 있는 오류에 따라 적절하게 대응하시기 바랍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//Inspired By www.androidhive.info
//Edited By Trendy Develope

<?php
 // db information  
$db_host = "localhost";  
$db_id = "root";  
$db_password = "password";  
$db_dbname = "my_database";  
 
// sleep for 2 sec show that the androd swipe refresh will be visible for sometime
sleep(2);
 
// reading offset from get parameter
$offset = isset($_GET['offset']) && $_GET['offset'!= '' ? $_GET['offset'] : 0;
$uid = $_GET["uid"];
 
// load limit
$limit = 5;
 
//read feed data from 'feed' table by uid
$query = "select * from feed where uid = '".$uid."'"
$result = mysqli_query ( $db_conn$query, MYSQLI_STORE_RESULT );
  
if (! $result) {  
    $message = 'Invalid query: ' . mysql_error () . "\n";  
    $message .= 'Whole query: ' . $query;  
    die ( $message );  
}  
  
// make json from database result set  
$resultArray = array ();
while ( $row = mysqli_fetch_assoc ( $result ) ) {  
    $arrayMiddle = array (  
            "id" => $row ['id'],
            "name" => $row ['name'],
            "image" => $row ['image'],
            "status" => $row ['image'],
            "profilePic" => $row ['profilePic'],
            "timestamp" => $row ['timestamp']
    );  
    array_push ( $resultArray$arrayMiddle );  
}
 
                                               //slice array //start  //Length
print_r( urldecode ( json_encode ( array_slice($resultArray$offset$offset+$limit) ) ) );
     //한글 출력을 위해
?>
cs

 

우선 MainActivity.class의 offSet 변수는 0으로 초기화되어있습니다. 이후 catchPosts 메소드에서 피드 한개를 불러올때마다 값이 +1 증가됩니다.


위의 PHP에서 LIMIT을 5로 정했기 때문에 피드 데이터가 부족하지 않은 이상 offSet변수는 한번의 사이클마다 값이 +5 증가하게 됩니다.

( 한번 당겨서 새로고침할 때마다 5개의 피드가 로드됩니다 )


이런 알고리즘을 감안하여 array_slice 메소드를 사용하면 데이터를 쉽게 잘라내어 로드할 수 있습니다.


urldecode 메소드와 json_encode 메소드는 각각 한글 출력, json 데이터 형식으로 변환을 위한 것입니다.

 

모든 절차를 완료했다면 다음과 같은 FEED를 볼 수 있습니다.