간단한 뉴스 앱 만들기 #3 - newsapi로 데이터 받아오기

먼저 adapter에 데이터를 보낼 때 뉴스 데이터를 넘겨야 한다.
뉴스 데이터들은 제목, 이미지, 내용을 포함하고 있다.
이를 객체화 해서 보내는 것이 효율적이므로 news data를 저장할 class를 만들어 두자.

package com.example.newstest;

import java.io.Serializable;

public class NewsData implements Serializable {
    private String title, urlToImage, content;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getUrlToImage() {
        return urlToImage;
    }

    public void setUrlToImage(String urlToImage) {
        this.urlToImage = urlToImage;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

newsapi.org에서 새로 가입을 하고 api를 얻을 수 있다. 해당 api주소를 저장해둔다.
그 주소에 대한 내용을 보면 다음과 같다.

이 내용에 대해서는 json 포스팅을 참고한다.

그리고, 우리는 volley를 통해 서버와 http 통신을 수행할 것이다.

volley

android developer에 따르면, "Volley는 Android 앱의 네트워킹을 더 쉽고, 무엇보다도 더 빠르게 하는 HTTP 라이브러리입니다." 라고 한다.

이를 이용하면 앱에서 서버와 http 통신을 더욱 간결하게 용이하게 할 수 있다.

먼저 앱에서 네트워크에 접속할 수 있도록 permission을 열어주어야 한다.
Manifest.xml에 <uses-permission android:name="android.permission.INTERNET" />를 추가한다.

그리고 volley를 이용하기 위해 다음을 추가한다.

implementation 'com.android.volley:volley:1.1.1'

RequestQueue를 생성해주어 우리가 요청할 데이터들을 저장해야한다.
여기에 저장을 해두면 이 RequestQueue가 알아서 요청하고 받아온다.

예제 코드를 보면, 다음과 같다.

RequestQueue queue;
queue = Volley.newRequestQueue(this); //context

String url = "";

// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
        new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                // Display the first 500 characters of the response string.
                //textView.setText("Response is: "+ response.substring(0,500);
                }
            }
        }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    System.out.println(error.getMessage());
                }
        }
);
// Add the request to the RequestQueue.
queue.add(stringRequest);

newsapi.org에서 보면 request 방식이 GET형식임을 알 수 있다. 우리도 get 형식에 맞추어 보내준다.
url에다 우리가 복사한 api주소를 넣어준다.

데이터를 정상적으로 받아왔을 때 onResponse함수가 작동하고, response 변수에 데이터가 string 형식으로 저장된다.

받아온 데이터가 json형태 이므로, json객체에 넣어 우리가 필요한 데이터들을 추출하자.
위 이미지에서 articles 요소에 우리가 필요한 데이터가 있다.
따라서, 다음과 같이 생성한다.

JSONArray arrayArticles = new JSONObject(response).getJSONArray("articles");

그리고 받아온 데이터들을 NewsData라는 객체에 각각 저장해두고 꺼내써야한다.
이 객체가 여러개 이므로 List라는 자료구조를 이용하자.

List<NewsData> newsList = new ArrayList<NewsData>();

자, 이제 for문을 이용해서 json배열에 있는 요소 하나하나 꺼내오자.

for (int i = 0; i < arrayArticles.length(); i++) {
    JSONObject obj = arrayArticles.getJSONObject(i);
    NewsData news = new NewsData();
    news.setTitle(obj.getString("title"));
    news.setUrlToImage(obj.getString("urlToImage"));
    news.setContent(obj.getString("description"));
    newsList.add(news);
}

그 후 adapter에 적용해주면 된다.

mAdapter = new MyAdapter(newsList, MainActivity.this);
recyclerView.setAdapter(mAdapter);

이 전체 코드가 onCreate함수 내에 있으면 조금 정리하기 힘드니 getNews라는 메소드를 만들어 따로 분리해주자.

전체 코드

package com.example.newstest;

import ...

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private RecyclerView.Adapter mAdapter;
    private RecyclerView.LayoutManager layoutManager;
    RequestQueue queue;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_news);

        recyclerView = findViewById(R.id.my_recycler_view);

        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        recyclerView.setHasFixedSize(true);

        // use a linear layout manager
        layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);

        queue = Volley.newRequestQueue(this);
        getNews();
    }

    public void getNews() {

        String url = "http://newsapi.org/v2/everything?q=bitcoin&from=2020-12-17&sortBy=publishedAt&apiKey=API_KEY";

        // Request a string response from the provided URL.
        StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        // Display the first 500 characters of the response string.
                        //textView.setText("Response is: "+ response.substring(0,500));
                        try {
                            List<NewsData> newsList = new ArrayList<NewsData>();
                            JSONArray arrayArticles = new JSONObject(response).getJSONArray("articles");

                            for (int i = 0; i < arrayArticles.length(); i++) {
                                JSONObject obj = arrayArticles.getJSONObject(i);

                                NewsData news = new NewsData();

                                news.setTitle(obj.getString("title"));
                                news.setUrlToImage(obj.getString("urlToImage"));
                                news.setContent(obj.getString("description"));

                                newsList.add(news);
                            }

                            // specify an adapter (see also next example)
                            mAdapter = new MyAdapter(newsList, MainActivity.this);
                            recyclerView.setAdapter(mAdapter);

                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                System.out.println(error.getMessage());
            }
        });

        // Add the request to the RequestQueue.
        queue.add(stringRequest);
    }

}

이제 어댑터에서 데이터를 받고 각 카드에 데이터들을 뿌려보자.


MyAdapter

위에서 adapter에 List와 context를 보내주었다. 그렇다면 여기서는 이 두가지 요소를 먼저 받아주어야 한다.

private List<NewsData> mDataset;

public MyAdapter(List<NewsData> myDataset, Context context) {
    mDataset = myDataset;
    Fresco.initialize(context);
    //fresco initialize는 기본적으로 이런 데이터 클래스에서 사용 못하고 activity에서 사용해야한다.
    //따라서 activity 주소를 받아와서 지정해준다.
    //메모리 누수의 가능성이 있어 추천하지 않음
}
  • 왜 context를 받아왔는가?
    • 우리가 이미지 로드에 사용할 Fresco때문이다.
    • fresco initialize는 기본적으로 이런 데이터 클래스에서 사용 못하고 activity에서 사용해야한다.
      따라서 activity 주소를 받아와서 지정해준다.
    • 메모리 누수의 가능성이 있어 추천하지 않음

 

그리고 viewholder class에 우리가 생성한 이미지, 제목, 내용 요소들의 주소를 받아온다.

public static class MyViewHolder extends RecyclerView.ViewHolder {
    public TextView TextView_Title;
    public TextView TextView_Content;
    public SimpleDraweeView ImageView_title;

    public MyViewHolder(LinearLayout v) {
        super(v);
        TextView_Title = v.findViewById(R.id.TextView_Title);
        TextView_Content = v.findViewById(R.id.TextView_Content);
        ImageView_title = v.findViewById(R.id.ImageView_Title);
    }
}

그 후 onBindViewHolder메소드에서 카드 하나의 디자인을 정해준다.

@Override
public void onBindViewHolder(MyViewHolder holder, int position) {

    NewsData news = mDataset.get(position);

    holder.TextView_Title.setText(news.getTitle());

    String content = (news.getContent() == null) ? "-" : news.getContent();
    holder.TextView_Content.setText(content);

    Uri uri = Uri.parse(news.getUrlToImage());
    holder.ImageView_title.setImageURI(uri);
}

fresco 이미지 로딩을 위해 url을 uri로 변환하여 넣어주어야 한다.
따라서, 위와 같이 생성한다.

전체 코드

package com.example.newstest;

import ...

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    private List<NewsData> mDataset;

    // Provide a reference to the views for each data item
    // Complex data items may need more than one view per item, and
    // you provide access to all the views for a data item in a view holder
    public static class MyViewHolder extends RecyclerView.ViewHolder {
        // each data item is just a string in this case
        public TextView TextView_Title;
        public TextView TextView_Content;
        public SimpleDraweeView ImageView_title;

        public MyViewHolder(LinearLayout v) {
            super(v);
            TextView_Title = v.findViewById(R.id.TextView_Title);
            TextView_Content = v.findViewById(R.id.TextView_Content);
            ImageView_title = v.findViewById(R.id.ImageView_Title);
        }
    }

    // Provide a suitable constructor (depends on the kind of dataset)
    public MyAdapter(List<NewsData> myDataset, Context context) {
        mDataset = myDataset;
        Fresco.initialize(context);
    }

    // Create new views (invoked by the layout manager)
    @Override
    public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,
                                                     int viewType) {
        // create a new view
        LinearLayout v = (LinearLayout) LayoutInflater.from(parent.getContext())
                .inflate(R.layout.row_news, parent, false);
        MyViewHolder vh = new MyViewHolder(v);
        return vh;
    }


    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element

        NewsData news = mDataset.get(position);

        holder.TextView_Title.setText(news.getTitle());

        String content = (news.getContent() == null) ? "-" : news.getContent();
        holder.TextView_Content.setText(content);


        Uri uri = Uri.parse(news.getUrlToImage());
        holder.ImageView_title.setImageURI(uri);
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return (mDataset == null) ? 0 : mDataset.size();
    }
}






에러: newsapi.org 사용불가...

 

newsapi.org 사용불가

newsapi.org에서 get방법을 통한 통신을 수행했으나, 전송이 되지 않았다. 그래서 내가 문제였나 싶어 json 테스트 사이트에서 테스트를 수행해 보았다. https://jsonplaceholder.typicode.com/ JSONPlaceholder -..

l-zzu-h.tistory.com

원래 newsapi.org에서 데이터를 받아올 예정이었으나 안되어 네이버 api를 통해 데이터를 받아온다.

전체 코드

//Main Activity
package com.example.newstest;

import ...

public class MainActivity extends AppCompatActivity {
    final String clientID = "개인 아이디";
    final String clientPassword = "개인 비밀번호";

    private RecyclerView recyclerView;
    private RecyclerView.Adapter mAdapter;
    private RecyclerView.LayoutManager layoutManager;
    RequestQueue queue;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_news);

        recyclerView = findViewById(R.id.my_recycler_view);

        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        recyclerView.setHasFixedSize(true);

        // use a linear layout manager
        layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);

        queue = Volley.newRequestQueue(this);
        getNews();
    }

    public void getNews() {

        String Search = "강아지";

        String url = "https://openapi.naver.com/v1/search/book.json?query=" + Search;

        // Request a string response from the provided URL.
        StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        // Display the first 500 characters of the response string.
                        //textView.setText("Response is: "+ response.substring(0,500));
                        Log.d("NEWS", response);

                        try {
                            List<NewsData> newsList = new ArrayList<NewsData>();
                            JSONArray arrayArticles = new JSONObject(response).getJSONArray("items");

                            for (int i = 0; i < arrayArticles.length(); i++) {
                                JSONObject obj = arrayArticles.getJSONObject(i);

                                NewsData news = new NewsData();

                                news.setTitle(obj.getString("title"));
                                news.setUrlToImage(obj.getString("image"));
                                news.setContent(obj.getString("description"));

                                newsList.add(news);
                            }

                            // specify an adapter (see also next example)
                            mAdapter = new MyAdapter(newsList, MainActivity.this);
                            recyclerView.setAdapter(mAdapter);

                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                System.out.println(error.getMessage());
            }
        }) {
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                HashMap<String, String> headers = new HashMap<String, String>();
                headers.put("X-Naver-Client-Id", clientID);
                headers.put("X-Naver-Client-Secret", clientPassword);
                return headers;
            }
        };

        // Add the request to the RequestQueue.
        queue.add(stringRequest);
    }

}

Adapter에서는 데이터 그대로 뿌려주면 되므로 위와 동일하다.

실행 화면

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기