[Android Studio,SpringBoot]KnockKnock開発ログ-0124(投稿とコメント閲覧機能を実現)


今日の目標


・\57
・
・𐥍ʥ・✔完全帖子公告板-各帖子をクリックすると詳細ページ+コメント(1回繰り返し)・𐥎・

リファレンスリンク


1.2種類のRecyclerView Clickイベント処理方法
2.Android Intentを使用してActivityを起動中にエラーが発生したAndroid。util.Android RuntimeException:Activityコンテキスト外部からStartActivity()を呼び出すにはFLAG ACTIVITY NEW TASKフラグを解決する必要があります

今日の話題

  • Ambiricy handlerメソッドホットスポット
  • Retrofit接続失敗
  • Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.
  • 今日開発されたコアは、掲示板で各投稿をクリックしたときに対応する投稿ページに移動することです.
    このほか、MainActivityですべての投稿を表示できる機能も実装されています.

    ここで、各投稿項目をクリックすると、対応する詳細な投稿ページに移動し、以下の内容が表示され、投稿のすべてのコメントが表示されます.

    じゃ、一度やってみましょう.🧚‍♀️🧚‍♀️⭐

    豚肉の炒め物の詳しい招待状のページを生成します


    1.PostListクエリのデータをpost PK(id)にインポートする


    既存のHTTP通信のために設計されたPostDataは、作成者のニックネーム、タイトル、コンテンツ、作成日時のみ通信します.ただし,各投稿のidは互いに送信されないため,後で個別に投稿を表示する機能を実現することは困難である.そのため、事前に伝言板形式のクエリーでは、各投稿にpkが受信され、各投稿をクリックすると、その投稿の詳細ページに移動し、その投稿とアップロードされた投稿を表示するように変更されます.

    🔺投稿のid値をAndroidから受信したPostDataに一致させる

    🔺Androidにid値を追加するPostSaveResponseクラス

    2.詳細な投稿ページレイアウトの生成(xml)


    掲示板で各投稿をクリックすると、投稿詳細ページのレイアウトが生成されます!
    ここの核心はコメントも確認できるようにすることです.

    まず.🤎デザインですが、データを持ち帰って撮るのが目標なので、それが実現しました.
    コメント部分はRecyclerViewで実現し、現在はRecyclerのコメントアイテムであるViewLayoutも制作されている.

    3.レビュー項目の作成ViewLayout(xml)



    多分こんなことになった!今では基本的なレイアウトフレームを作ることに慣れているようです(制作速度が速い)
    recyclerviewに入るviewプロジェクトを作成する場合、
  • view項目がどのようにソートされるかによって、一番上のlinellayoutを設定したほうがいいです.
    :コメントを垂直に設定します.下に置くからです.
  • また、コメントボックスは1ページに複数作成する必要があるため、以降作成するコメントボックスについては、heightをユーザ設定の値に設定する必要がある.経験的には100 dp程度が適当です.
  • 4.サーバ上でのapiの実装


    ああ...この部分には体現すべきものがたくさんある.まず,サーバ側は,各投稿クエリに対するapiを開発していない.したがって、各投稿はクエリーのapiを実装し、その応答dtoに対して、各投稿とコメントは個別に実装されます.忙しい忙しい忙しい
      //게시글 하나 detail 정보 조회
        @GetMapping("api/post/view/{postId}")
        public PostViewResponse viewEachPost(@PathVariable("postId")Long id){
            Post post = postService.getPostById(id);
            PostViewResponse response =  new PostViewResponse(post.getId(),
                   post.getPostwriter().getNickName(),
                    post.getTitle(),
                    post.getContent(),
                    post.getTimestamp());
            List<Comment> comments = post.getPostcomments();
            List<CommentDto> commentDtoList = comments.stream().map(c->new CommentDto(c.getId(),c.getCommentwriter().getNickName(),c.getTimestamp(),c.getContent()))
                    .collect(Collectors.toList());
            response.setCommentlist(commentDtoList);
            return response;
        }
    どのように低カードの実装を完了するかは、POSTMANでテストされ、次のエラーが発生しました.

    Ambirichandlerメソッドホットスポット

    java.lang.IllegalStateException: Ambiguous handler methods mapped for '/api/post/view/2': {public jpaproject.knockknock.api.response.PostViewResponse jpaproject.knockknock.api.PostController.viewEachPost(java.lang.Long), public jpaproject.knockknock.api.PostController$Result jpaproject.knockknock.api.PostController.viewPostofWriter(java.lang.String)}
    	at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:432)
    	at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:383)
    	at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:125)
    	at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:67)
    	at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:498)
    	at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1261)
    	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1043)
    	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
    	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:655)
    	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
    	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
    	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
    	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
    	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
    	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
    	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
    	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895)
    	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1722)
    	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
    	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    	at java.base/java.lang.Thread.run(Thread.java:834)
    Googleで見ると、ホットスポットは同じurlが重複して存在します.
    調べてみると、実際に同じurlが繰り返し使用されていることがわかりました.
    開発はやはり最も難しい.

    紆余曲折を経て,やっとサーバ側の開発が完了した.

    5.AndroidでRecyclerViewのRetrofitを実施


  • Data Classの作成
    上記のjson形式応答に基づいて,CommentDataとCommentDataをリストとするPostDetailDataクラスを実現した.

  • apiインタフェースの作成
  •  @GET("api/post/view/{postId}")
        Call<PostDetailData> getPostDatabyPostId(@Path("postId")Long id);
    現在最も実現しやすいAPI(オブジェクト向け、コード効率は考慮されていません)

    6.RecyclerViewでのClickイベントの処理

  • まずItemClickListenerインタフェースを作成する.
  • package org.techtown.knockknock;
    
    import android.view.View;
    
    public interface ItemClickListener {
        void onItemClickListener(View v, int position);
    }
    
  • RecyclerView Adapter(掲示板Recyclerview)におけるクリックイベント処理コード
  • AdapterのHolderセクション
    HolderでItemClickListerオブジェクトを作成し、Holderで作成したListViewでonClickListerを設定します.その後、onClickメソッドでitemClickListenerを使用してビューとlayoutpositionをオブジェクトに設定します.
    //Holder: 레이아웃과 연결해서 listView를 만들어주는 역할 (단순 연결)
        public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
    
            TextView title;
            TextView content;
            TextView date;
    
            ItemClickListener itemClickListener; //📌
    
            public MyViewHolder(@NonNull View itemView) {
                super(itemView);
    
                title = (TextView)itemView.findViewById(R.id.tv_postlist_title);
                content = (TextView)itemView.findViewById(R.id.tv_postlist_content);
                date = (TextView)itemView.findViewById(R.id.tv_postlist_timestamp);
    
                itemView.setOnClickListener(this); //📌
            }
            @Override //📌
            public void onClick(View v){
                this.itemClickListener.onItemClickListener(v,getLayoutPosition());
            }
        }
    AdapterのonBindViewHolderメソッドセクション
    holderのitemClickListener変数を設定します.新しいItemClickListenerオブジェクトを作成し、onItemClickListenerメソッドの上で実装したいコンテンツを実装すればよい.
    positionはlist形式で含まれるデータのインデックスであるため、list形式で含まれるデータの1つにアクセスする場合にlistを使用することができます.get(position).getXX(); 形で近づけばいい
    私が実現した内容は、アクセスする投稿のIDを取得し、intentをPostDetailActivityとしてページをめくり、その時のようにその投稿のIDを取得し、新しいPostDetailActivityから新しいPostDetailActivityに移動し、新しい投稿の詳細データをクエリー出力のシナリオとして実現することです.
    @Override
    public void onBindViewHolder(@NonNull RecyclerAdapter.MyViewHolder holder, int position){
        holder.title.setText(postlist.get(position).getTitle());
        holder.date.setText(postlist.get(position).getDate());
        holder.content.setText(postlist.get(position).getContent());
    
    
        holder.itemClickListener = new ItemClickListener() { //📌
            @Override //📌
            public void onItemClickListener(View v, int position) {
    
                Long postId = postlist.get(position).getId();
                Intent intent = new Intent(v.getContext(),PostdetailActivity.class);
                intent.putExtra("postid",postId);
                v.getContext().startActivity(intent);
            }
        };
    }

    7.PostDetailActivityの実装


    コードは多いですが、Retrofit実装+RecyclerViewを繰り返し使用するので、具体的な説明は省略します!今はコードだけ見ても理解できるようになりました…!(違うの?😧)
    ここのコアは📌ピンの部分!Intentに移動する以上、一緒に移動したpostのidで関連データを再問合せして出力します.
    package org.techtown.knockknock.post;
    
    import androidx.appcompat.app.AppCompatActivity;
    import androidx.recyclerview.widget.LinearLayoutManager;
    import androidx.recyclerview.widget.RecyclerView;
    
    import android.content.Intent;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.TextView;
    
    import com.google.gson.Gson;
    
    import org.techtown.knockknock.ErrorBody;
    import org.techtown.knockknock.R;
    import org.techtown.knockknock.RetrofitClient;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import retrofit2.Call;
    import retrofit2.Callback;
    import retrofit2.Response;
    
    public class PostdetailActivity extends AppCompatActivity {
    
        TextView title;
        TextView writer;
        TextView timestamp;
        TextView content;
        List<CommentData> comments;
    
        RecyclerView recyclerView;
        CommentRecyclerAdapter recyclerAdapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_postdetail);
    
            comments = new ArrayList<>();
            recyclerView = findViewById(R.id.recyclerView_postdetail);
    
            RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
            recyclerView.setLayoutManager(layoutManager);
    
    	//📌
            Intent intent = getIntent();
            Long postid = intent.getExtras().getLong("postid");
            
            PostAPI postAPI = RetrofitClient.getInstance().create(PostAPI.class);
            Call<PostDetailData> call = postAPI.getPostDatabyPostId(postid);
            call.enqueue(new Callback<PostDetailData>() {
                @Override
                public void onResponse(Call<PostDetailData> call, Response<PostDetailData> response) {
                    if(response.isSuccessful()){
    
                        PostDetailData postdata = response.body();
    
                        title = findViewById(R.id.tv_postdetail_title);
                        writer = findViewById(R.id.tv_postdetail_writer);
                        timestamp = findViewById(R.id.tv_postdetail_timestamp);
                        content = findViewById(R.id.tv_postdetail_content);
    
                        title.setText(postdata.getPostTitle());
                        content.setText(postdata.getPostContent());
                        writer.setText(postdata.getPostwriter());
                        timestamp.setText(postdata.getPostedTime());
                        comments = postdata.comments;
    
                        recyclerAdapter = new CommentRecyclerAdapter(getApplicationContext(),comments);
                        recyclerView.setAdapter(recyclerAdapter);
    
    
                    }
                    else{
                        ErrorBody error = new Gson().fromJson(response.errorBody().charStream(),ErrorBody.class);
                        Log.d("PostdetailActivity",error.getMessage());
                    }
                }
    
                @Override
                public void onFailure(Call<PostDetailData> call, Throwable t) {
                    Log.d("PostdetailActivity","게시글 조회 연결 실패");
    
                }
            });
        }
    }

    Retrofit接続に失敗しました



    突然、うまく改造できない不幸なことが起こった.
    私はコードに触れず、既存のログインページからログインに失敗し、気が狂ったと思った.
    グーグルゲームを頑張ってもいい方法はない...
    検索したところ、テストしたデバイスがネットワークipと異なると、そうなる可能性があることが分かった.
    現在Tozz受信ネットワークipではランダム配布の各機器を使用しているなぜダメなのかはわかりませんが、この問題で打たれ、仮想マシンでアンドロイドを回すと、また通信がうまくいきます.
    よくわからないけど仮想マシンで回ってみよう😢

    Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.


    フォーラムリストの各投稿項目をクリックして詳細な投稿アクティビティに移動しようとすると、次のエラーが発生し、アプリケーションが閉じます.
    2022-01-24 15:45:17.173 14232-14232/? E/AndroidRuntime: FATAL EXCEPTION: main
       Process: org.techtown.knockknock, PID: 14232
       android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
           at android.app.ContextImpl.startActivity(ContextImpl.java:952)
           at android.app.ContextImpl.startActivity(ContextImpl.java:928)
           at android.content.ContextWrapper.startActivity(ContextWrapper.java:383)
           at org.techtown.knockknock.post.RecyclerAdapter$1.onItemClickListener(RecyclerAdapter.java:57)
           at org.techtown.knockknock.post.RecyclerAdapter$MyViewHolder.onClick(RecyclerAdapter.java:88)
           at android.view.View.performClick(View.java:7125)
           at android.view.View.performClickInternal(View.java:7102)
           at android.view.View.access$3500(View.java:801)
           at android.view.View$PerformClick.run(View.java:27336)
           at android.os.Handler.handleCallback(Handler.java:883)
           at android.os.Handler.dispatchMessage(Handler.java:100)
           at android.os.Looper.loop(Looper.java:214)
           at android.app.ActivityThread.main(ActivityThread.java:7356)
           at java.lang.reflect.Method.invoke(Native Method)
           at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
           at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
    この問題はActivityではなく、別の場所でStartActivityを行うために爆発した問題です.
    実際、問題が発生した部分は、RecyclerAdapterでItemViewをクリックしたときに、その投稿の詳細なアクティビティとしてintentを移動しようとしたことです.
    幸いこの問題は簡単に解決できて、もとはintentの部分だけにあげますintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))だけあげればいい.
    v.getContext().startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    やっと伝言板の招待状のリストの中で1つの招待状をクリックして招待状の详しい画面の出力と相応の招待状の招待状のサービスを见ることができます!!!🤸‍♀️💃🎇🎆
    もちろん、投稿の画像、タグ処理、UI修正、小さな情報修正など、修正すべき点はたくさんありますが、まず大きな枠組みの下で大きな進歩を遂げましたハハ、幸せです

    投稿全体を参照し、各投稿に移動


    この部分は実は今までに体現した部分とたくさん重なって、すぐに実現しました!
    特にRecyclerViewでは,各投稿viewItemのフォーマットは前に作成したフォーマットと同じであり,HolderやAdapterに触れる必要はなく,同様に各投稿の移動も同様である.
    変更はありません.APIだけが完全な投稿クエリーAPIを作成し、対応するAPIを使用してクエリーします.残りは同じだよ
    @GET("api/post/view")
       Call<PostListData> getPostList();